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