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