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