1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.support.v4.view;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.support.annotation.ColorRes;
25 import android.support.annotation.DrawableRes;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewConfiguration;
30 
31 /**
32  * PagerTabStrip is an interactive indicator of the current, next,
33  * and previous pages of a {@link ViewPager}. It is intended to be used as a
34  * child view of a ViewPager widget in your XML layout.
35  * Add it as a child of a ViewPager in your layout file and set its
36  * android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom
37  * of the ViewPager. The title from each page is supplied by the method
38  * {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to
39  * the ViewPager.
40  *
41  * <p>For a non-interactive indicator, see {@link PagerTitleStrip}.</p>
42  */
43 public class PagerTabStrip extends PagerTitleStrip {
44     private static final String TAG = "PagerTabStrip";
45 
46     private static final int INDICATOR_HEIGHT = 3; // dp
47     private static final int MIN_PADDING_BOTTOM = INDICATOR_HEIGHT + 3; // dp
48     private static final int TAB_PADDING = 16; // dp
49     private static final int TAB_SPACING = 32; // dp
50     private static final int MIN_TEXT_SPACING = TAB_SPACING + TAB_PADDING * 2; // dp
51     private static final int FULL_UNDERLINE_HEIGHT = 1; // dp
52     private static final int MIN_STRIP_HEIGHT = 32; // dp
53 
54     private int mIndicatorColor;
55     private int mIndicatorHeight;
56 
57     private int mMinPaddingBottom;
58     private int mMinTextSpacing;
59     private int mMinStripHeight;
60 
61     private int mTabPadding;
62 
63     private final Paint mTabPaint = new Paint();
64     private final Rect mTempRect = new Rect();
65 
66     private int mTabAlpha = 0xFF;
67 
68     private boolean mDrawFullUnderline = false;
69     private boolean mDrawFullUnderlineSet = false;
70     private int mFullUnderlineHeight;
71 
72     private boolean mIgnoreTap;
73     private float mInitialMotionX;
74     private float mInitialMotionY;
75     private int mTouchSlop;
76 
PagerTabStrip(Context context)77     public PagerTabStrip(Context context) {
78         this(context, null);
79     }
80 
PagerTabStrip(Context context, AttributeSet attrs)81     public PagerTabStrip(Context context, AttributeSet attrs) {
82         super(context, attrs);
83 
84         mIndicatorColor = mTextColor;
85         mTabPaint.setColor(mIndicatorColor);
86 
87         // Note: this follows the rules for Resources#getDimensionPixelOffset/Size:
88         //       sizes round up, offsets round down.
89         final float density = context.getResources().getDisplayMetrics().density;
90         mIndicatorHeight = (int) (INDICATOR_HEIGHT * density + 0.5f);
91         mMinPaddingBottom = (int) (MIN_PADDING_BOTTOM * density + 0.5f);
92         mMinTextSpacing = (int) (MIN_TEXT_SPACING * density);
93         mTabPadding = (int) (TAB_PADDING * density + 0.5f);
94         mFullUnderlineHeight = (int) (FULL_UNDERLINE_HEIGHT * density + 0.5f);
95         mMinStripHeight = (int) (MIN_STRIP_HEIGHT * density + 0.5f);
96         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
97 
98         // Enforce restrictions
99         setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
100         setTextSpacing(getTextSpacing());
101 
102         setWillNotDraw(false);
103 
104         mPrevText.setFocusable(true);
105         mPrevText.setOnClickListener(new OnClickListener() {
106             @Override
107             public void onClick(View v) {
108                 mPager.setCurrentItem(mPager.getCurrentItem() - 1);
109             }
110         });
111 
112         mNextText.setFocusable(true);
113         mNextText.setOnClickListener(new OnClickListener() {
114             @Override
115             public void onClick(View v) {
116                 mPager.setCurrentItem(mPager.getCurrentItem() + 1);
117             }
118         });
119 
120         if (getBackground() == null) {
121             mDrawFullUnderline = true;
122         }
123     }
124 
125     /**
126      * Set the color of the tab indicator bar.
127      *
128      * @param color Color to set as an 0xRRGGBB value. The high byte (alpha) is ignored.
129      */
setTabIndicatorColor(int color)130     public void setTabIndicatorColor(int color) {
131         mIndicatorColor = color;
132         mTabPaint.setColor(mIndicatorColor);
133         invalidate();
134     }
135 
136     /**
137      * Set the color of the tab indicator bar from a color resource.
138      *
139      * @param resId Resource ID of a color resource to load
140      */
setTabIndicatorColorResource(@olorRes int resId)141     public void setTabIndicatorColorResource(@ColorRes int resId) {
142         setTabIndicatorColor(getContext().getResources().getColor(resId));
143     }
144 
145     /**
146      * @return The current tab indicator color as an 0xRRGGBB value.
147      */
getTabIndicatorColor()148     public int getTabIndicatorColor() {
149         return mIndicatorColor;
150     }
151 
152     @Override
setPadding(int left, int top, int right, int bottom)153     public void setPadding(int left, int top, int right, int bottom) {
154         if (bottom < mMinPaddingBottom) {
155             bottom = mMinPaddingBottom;
156         }
157         super.setPadding(left, top, right, bottom);
158     }
159 
160     @Override
setTextSpacing(int textSpacing)161     public void setTextSpacing(int textSpacing) {
162         if (textSpacing < mMinTextSpacing) {
163             textSpacing = mMinTextSpacing;
164         }
165         super.setTextSpacing(textSpacing);
166     }
167 
168     @Override
setBackgroundDrawable(Drawable d)169     public void setBackgroundDrawable(Drawable d) {
170         super.setBackgroundDrawable(d);
171         if (!mDrawFullUnderlineSet) {
172             mDrawFullUnderline = d == null;
173         }
174     }
175 
176     @Override
setBackgroundColor(int color)177     public void setBackgroundColor(int color) {
178         super.setBackgroundColor(color);
179         if (!mDrawFullUnderlineSet) {
180             mDrawFullUnderline = (color & 0xFF000000) == 0;
181         }
182     }
183 
184     @Override
setBackgroundResource(@rawableRes int resId)185     public void setBackgroundResource(@DrawableRes int resId) {
186         super.setBackgroundResource(resId);
187         if (!mDrawFullUnderlineSet) {
188             mDrawFullUnderline = resId == 0;
189         }
190     }
191 
192     /**
193      * Set whether this tab strip should draw a full-width underline in the
194      * current tab indicator color.
195      *
196      * @param drawFull true to draw a full-width underline, false otherwise
197      */
setDrawFullUnderline(boolean drawFull)198     public void setDrawFullUnderline(boolean drawFull) {
199         mDrawFullUnderline = drawFull;
200         mDrawFullUnderlineSet = true;
201         invalidate();
202     }
203 
204     /**
205      * Return whether or not this tab strip will draw a full-width underline.
206      * This defaults to true if no background is set.
207      *
208      * @return true if this tab strip will draw a full-width underline in the
209      * current tab indicator color.
210      */
getDrawFullUnderline()211     public boolean getDrawFullUnderline() {
212         return mDrawFullUnderline;
213     }
214 
215     @Override
getMinHeight()216     int getMinHeight() {
217         return Math.max(super.getMinHeight(), mMinStripHeight);
218     }
219 
220     @Override
onTouchEvent(MotionEvent ev)221     public boolean onTouchEvent(MotionEvent ev) {
222         final int action = ev.getAction();
223         if (action != MotionEvent.ACTION_DOWN && mIgnoreTap) {
224             return false;
225         }
226 
227         // Any tap within touch slop to either side of the current item
228         // will scroll to prev/next.
229         final float x = ev.getX();
230         final float y = ev.getY();
231         switch (action) {
232             case MotionEvent.ACTION_DOWN:
233                 mInitialMotionX = x;
234                 mInitialMotionY = y;
235                 mIgnoreTap = false;
236                 break;
237 
238             case MotionEvent.ACTION_MOVE:
239                 if (Math.abs(x - mInitialMotionX) > mTouchSlop ||
240                         Math.abs(y - mInitialMotionY) > mTouchSlop) {
241                     mIgnoreTap = true;
242                 }
243                 break;
244 
245             case MotionEvent.ACTION_UP:
246                 if (x < mCurrText.getLeft() - mTabPadding) {
247                     mPager.setCurrentItem(mPager.getCurrentItem() - 1);
248                 } else if (x > mCurrText.getRight() + mTabPadding) {
249                     mPager.setCurrentItem(mPager.getCurrentItem() + 1);
250                 }
251                 break;
252         }
253 
254         return true;
255     }
256 
257     @Override
onDraw(Canvas canvas)258     protected void onDraw(Canvas canvas) {
259         super.onDraw(canvas);
260 
261         final int height = getHeight();
262         final int bottom = height;
263         final int left = mCurrText.getLeft() - mTabPadding;
264         final int right = mCurrText.getRight() + mTabPadding;
265         final int top = bottom - mIndicatorHeight;
266 
267         mTabPaint.setColor(mTabAlpha << 24 | (mIndicatorColor & 0xFFFFFF));
268         canvas.drawRect(left, top, right, bottom, mTabPaint);
269 
270         if (mDrawFullUnderline) {
271             mTabPaint.setColor(0xFF << 24 | (mIndicatorColor & 0xFFFFFF));
272             canvas.drawRect(getPaddingLeft(), height - mFullUnderlineHeight,
273                     getWidth() - getPaddingRight(), height, mTabPaint);
274         }
275     }
276 
277     @Override
updateTextPositions(int position, float positionOffset, boolean force)278     void updateTextPositions(int position, float positionOffset, boolean force) {
279         final Rect r = mTempRect;
280         int bottom = getHeight();
281         int left = mCurrText.getLeft() - mTabPadding;
282         int right = mCurrText.getRight() + mTabPadding;
283         int top = bottom - mIndicatorHeight;
284 
285         r.set(left, top, right, bottom);
286 
287         super.updateTextPositions(position, positionOffset, force);
288         mTabAlpha = (int) (Math.abs(positionOffset - 0.5f) * 2 * 0xFF);
289 
290         left = mCurrText.getLeft() - mTabPadding;
291         right = mCurrText.getRight() + mTabPadding;
292         r.union(left, top, right, bottom);
293 
294         invalidate(r);
295     }
296 }
297