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