| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.v4.view; |
| |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.support.annotation.ColorInt; |
| import android.support.annotation.ColorRes; |
| import android.support.annotation.DrawableRes; |
| import android.support.v4.content.ContextCompat; |
| import android.util.AttributeSet; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| |
| /** |
| * PagerTabStrip is an interactive indicator of the current, next, |
| * and previous pages of a {@link ViewPager}. It is intended to be used as a |
| * child view of a ViewPager widget in your XML layout. |
| * Add it as a child of a ViewPager in your layout file and set its |
| * android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom |
| * of the ViewPager. The title from each page is supplied by the method |
| * {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to |
| * the ViewPager. |
| * |
| * <p>For a non-interactive indicator, see {@link PagerTitleStrip}.</p> |
| */ |
| public class PagerTabStrip extends PagerTitleStrip { |
| private static final String TAG = "PagerTabStrip"; |
| |
| private static final int INDICATOR_HEIGHT = 3; // dp |
| private static final int MIN_PADDING_BOTTOM = INDICATOR_HEIGHT + 3; // dp |
| private static final int TAB_PADDING = 16; // dp |
| private static final int TAB_SPACING = 32; // dp |
| private static final int MIN_TEXT_SPACING = TAB_SPACING + TAB_PADDING * 2; // dp |
| private static final int FULL_UNDERLINE_HEIGHT = 1; // dp |
| private static final int MIN_STRIP_HEIGHT = 32; // dp |
| |
| private int mIndicatorColor; |
| private int mIndicatorHeight; |
| |
| private int mMinPaddingBottom; |
| private int mMinTextSpacing; |
| private int mMinStripHeight; |
| |
| private int mTabPadding; |
| |
| private final Paint mTabPaint = new Paint(); |
| private final Rect mTempRect = new Rect(); |
| |
| private int mTabAlpha = 0xFF; |
| |
| private boolean mDrawFullUnderline = false; |
| private boolean mDrawFullUnderlineSet = false; |
| private int mFullUnderlineHeight; |
| |
| private boolean mIgnoreTap; |
| private float mInitialMotionX; |
| private float mInitialMotionY; |
| private int mTouchSlop; |
| |
| public PagerTabStrip(Context context) { |
| this(context, null); |
| } |
| |
| public PagerTabStrip(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| mIndicatorColor = mTextColor; |
| mTabPaint.setColor(mIndicatorColor); |
| |
| // Note: this follows the rules for Resources#getDimensionPixelOffset/Size: |
| // sizes round up, offsets round down. |
| final float density = context.getResources().getDisplayMetrics().density; |
| mIndicatorHeight = (int) (INDICATOR_HEIGHT * density + 0.5f); |
| mMinPaddingBottom = (int) (MIN_PADDING_BOTTOM * density + 0.5f); |
| mMinTextSpacing = (int) (MIN_TEXT_SPACING * density); |
| mTabPadding = (int) (TAB_PADDING * density + 0.5f); |
| mFullUnderlineHeight = (int) (FULL_UNDERLINE_HEIGHT * density + 0.5f); |
| mMinStripHeight = (int) (MIN_STRIP_HEIGHT * density + 0.5f); |
| mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); |
| |
| // Enforce restrictions |
| setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); |
| setTextSpacing(getTextSpacing()); |
| |
| setWillNotDraw(false); |
| |
| mPrevText.setFocusable(true); |
| mPrevText.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mPager.setCurrentItem(mPager.getCurrentItem() - 1); |
| } |
| }); |
| |
| mNextText.setFocusable(true); |
| mNextText.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mPager.setCurrentItem(mPager.getCurrentItem() + 1); |
| } |
| }); |
| |
| if (getBackground() == null) { |
| mDrawFullUnderline = true; |
| } |
| } |
| |
| /** |
| * Set the color of the tab indicator bar. |
| * |
| * @param color Color to set as an 0xRRGGBB value. The high byte (alpha) is ignored. |
| */ |
| public void setTabIndicatorColor(@ColorInt int color) { |
| mIndicatorColor = color; |
| mTabPaint.setColor(mIndicatorColor); |
| invalidate(); |
| } |
| |
| /** |
| * Set the color of the tab indicator bar from a color resource. |
| * |
| * @param resId Resource ID of a color resource to load |
| */ |
| public void setTabIndicatorColorResource(@ColorRes int resId) { |
| setTabIndicatorColor(ContextCompat.getColor(getContext(), resId)); |
| } |
| |
| /** |
| * @return The current tab indicator color as an 0xRRGGBB value. |
| */ |
| @ColorInt |
| public int getTabIndicatorColor() { |
| return mIndicatorColor; |
| } |
| |
| @Override |
| public void setPadding(int left, int top, int right, int bottom) { |
| if (bottom < mMinPaddingBottom) { |
| bottom = mMinPaddingBottom; |
| } |
| super.setPadding(left, top, right, bottom); |
| } |
| |
| @Override |
| public void setTextSpacing(int textSpacing) { |
| if (textSpacing < mMinTextSpacing) { |
| textSpacing = mMinTextSpacing; |
| } |
| super.setTextSpacing(textSpacing); |
| } |
| |
| @Override |
| public void setBackgroundDrawable(Drawable d) { |
| super.setBackgroundDrawable(d); |
| if (!mDrawFullUnderlineSet) { |
| mDrawFullUnderline = d == null; |
| } |
| } |
| |
| @Override |
| public void setBackgroundColor(@ColorInt int color) { |
| super.setBackgroundColor(color); |
| if (!mDrawFullUnderlineSet) { |
| mDrawFullUnderline = (color & 0xFF000000) == 0; |
| } |
| } |
| |
| @Override |
| public void setBackgroundResource(@DrawableRes int resId) { |
| super.setBackgroundResource(resId); |
| if (!mDrawFullUnderlineSet) { |
| mDrawFullUnderline = resId == 0; |
| } |
| } |
| |
| /** |
| * Set whether this tab strip should draw a full-width underline in the |
| * current tab indicator color. |
| * |
| * @param drawFull true to draw a full-width underline, false otherwise |
| */ |
| public void setDrawFullUnderline(boolean drawFull) { |
| mDrawFullUnderline = drawFull; |
| mDrawFullUnderlineSet = true; |
| invalidate(); |
| } |
| |
| /** |
| * Return whether or not this tab strip will draw a full-width underline. |
| * This defaults to true if no background is set. |
| * |
| * @return true if this tab strip will draw a full-width underline in the |
| * current tab indicator color. |
| */ |
| public boolean getDrawFullUnderline() { |
| return mDrawFullUnderline; |
| } |
| |
| @Override |
| int getMinHeight() { |
| return Math.max(super.getMinHeight(), mMinStripHeight); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| final int action = ev.getAction(); |
| if (action != MotionEvent.ACTION_DOWN && mIgnoreTap) { |
| return false; |
| } |
| |
| // Any tap within touch slop to either side of the current item |
| // will scroll to prev/next. |
| final float x = ev.getX(); |
| final float y = ev.getY(); |
| switch (action) { |
| case MotionEvent.ACTION_DOWN: |
| mInitialMotionX = x; |
| mInitialMotionY = y; |
| mIgnoreTap = false; |
| break; |
| |
| case MotionEvent.ACTION_MOVE: |
| if (Math.abs(x - mInitialMotionX) > mTouchSlop |
| || Math.abs(y - mInitialMotionY) > mTouchSlop) { |
| mIgnoreTap = true; |
| } |
| break; |
| |
| case MotionEvent.ACTION_UP: |
| if (x < mCurrText.getLeft() - mTabPadding) { |
| mPager.setCurrentItem(mPager.getCurrentItem() - 1); |
| } else if (x > mCurrText.getRight() + mTabPadding) { |
| mPager.setCurrentItem(mPager.getCurrentItem() + 1); |
| } |
| break; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| super.onDraw(canvas); |
| |
| final int height = getHeight(); |
| final int bottom = height; |
| final int left = mCurrText.getLeft() - mTabPadding; |
| final int right = mCurrText.getRight() + mTabPadding; |
| final int top = bottom - mIndicatorHeight; |
| |
| mTabPaint.setColor(mTabAlpha << 24 | (mIndicatorColor & 0xFFFFFF)); |
| canvas.drawRect(left, top, right, bottom, mTabPaint); |
| |
| if (mDrawFullUnderline) { |
| mTabPaint.setColor(0xFF << 24 | (mIndicatorColor & 0xFFFFFF)); |
| canvas.drawRect(getPaddingLeft(), height - mFullUnderlineHeight, |
| getWidth() - getPaddingRight(), height, mTabPaint); |
| } |
| } |
| |
| @Override |
| void updateTextPositions(int position, float positionOffset, boolean force) { |
| final Rect r = mTempRect; |
| int bottom = getHeight(); |
| int left = mCurrText.getLeft() - mTabPadding; |
| int right = mCurrText.getRight() + mTabPadding; |
| int top = bottom - mIndicatorHeight; |
| |
| r.set(left, top, right, bottom); |
| |
| super.updateTextPositions(position, positionOffset, force); |
| mTabAlpha = (int) (Math.abs(positionOffset - 0.5f) * 2 * 0xFF); |
| |
| left = mCurrText.getLeft() - mTabPadding; |
| right = mCurrText.getRight() + mTabPadding; |
| r.union(left, top, right, bottom); |
| |
| invalidate(r); |
| } |
| } |