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