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