PagerTitleStrip.java revision a4a06a94df00575480d789b60ea25ce59184df1f
1/* 2 * Copyright (C) 2011 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.content.res.TypedArray; 21import android.database.DataSetObserver; 22import android.graphics.drawable.Drawable; 23import android.text.TextUtils.TruncateAt; 24import android.util.AttributeSet; 25import android.util.TypedValue; 26import android.view.ViewGroup; 27import android.view.ViewParent; 28import android.widget.TextView; 29 30/** 31 * PagerTitleStrip is a non-interactive indicator of the current, next, 32 * and previous pages of a {@link ViewPager}. It is intended to be used as a 33 * child view of a ViewPager widget in your XML layout. 34 * Add it as a child of a ViewPager in your layout file and set its 35 * android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom 36 * of the ViewPager. The title from each page is supplied by the method 37 * {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to 38 * the ViewPager. 39 */ 40public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor { 41 private static final String TAG = "PagerTitleStrip"; 42 43 ViewPager mPager; 44 private TextView mPrevText; 45 private TextView mCurrText; 46 private TextView mNextText; 47 48 private int mLastKnownCurrentPage = -1; 49 private float mLastKnownPositionOffset = -1; 50 private int mScaledTextSpacing; 51 52 private boolean mUpdatingText; 53 private boolean mUpdatingPositions; 54 55 private final PageListener mPageListener = new PageListener(); 56 57 private static final int[] ATTRS = new int[] { 58 android.R.attr.textAppearance, 59 android.R.attr.textColor, 60 android.R.attr.textSize 61 }; 62 63 private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 = invisible, FF = opaque 64 private static final int TEXT_SPACING = 16; // dip 65 66 private int mNonPrimaryAlpha = SIDE_ALPHA; 67 private int mTextColor; 68 69 public PagerTitleStrip(Context context) { 70 this(context, null); 71 } 72 73 public PagerTitleStrip(Context context, AttributeSet attrs) { 74 super(context, attrs); 75 76 addView(mPrevText = new TextView(context)); 77 addView(mCurrText = new TextView(context)); 78 addView(mNextText = new TextView(context)); 79 80 final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); 81 final int textAppearance = a.getResourceId(0, 0); 82 if (textAppearance != 0) { 83 mPrevText.setTextAppearance(context, textAppearance); 84 mCurrText.setTextAppearance(context, textAppearance); 85 mNextText.setTextAppearance(context, textAppearance); 86 } 87 if (a.hasValue(1)) { 88 final int textColor = a.getColor(1, 0); 89 mPrevText.setTextColor(textColor); 90 mCurrText.setTextColor(textColor); 91 mNextText.setTextColor(textColor); 92 } 93 final int textSize = a.getDimensionPixelSize(2, 0); 94 if (textSize != 0) { 95 mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 96 mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 97 mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 98 } 99 a.recycle(); 100 101 mTextColor = mCurrText.getTextColors().getDefaultColor(); 102 setNonPrimaryAlpha(SIDE_ALPHA); 103 104 mPrevText.setEllipsize(TruncateAt.END); 105 mCurrText.setEllipsize(TruncateAt.END); 106 mNextText.setEllipsize(TruncateAt.END); 107 mPrevText.setSingleLine(); 108 mCurrText.setSingleLine(); 109 mNextText.setSingleLine(); 110 111 final float density = context.getResources().getDisplayMetrics().density; 112 mScaledTextSpacing = (int) (TEXT_SPACING * density); 113 } 114 115 /** 116 * Set the required spacing between title segments. 117 * 118 * @param spacingPixels Spacing between each title displayed in pixels 119 */ 120 public void setTextSpacing(int spacingPixels) { 121 mScaledTextSpacing = spacingPixels; 122 requestLayout(); 123 } 124 125 /** 126 * Set the alpha value used for non-primary page titles. 127 * 128 * @param alpha Opacity value in the range 0-1f 129 */ 130 public void setNonPrimaryAlpha(float alpha) { 131 mNonPrimaryAlpha = (int) (alpha * 255) & 0xFF; 132 final int transparentColor = (mNonPrimaryAlpha << 24) | (mTextColor & 0xFFFFFF); 133 mPrevText.setTextColor(transparentColor); 134 mNextText.setTextColor(transparentColor); 135 } 136 137 /** 138 * Set the color value used as the base color for all displayed page titles. 139 * Alpha will be ignored for non-primary page titles. See {@link #setNonPrimaryAlpha(float)}. 140 * 141 * @param color Color hex code in 0xAARRGGBB format 142 */ 143 public void setTextColor(int color) { 144 mTextColor = color; 145 mCurrText.setTextColor(color); 146 final int transparentColor = (mNonPrimaryAlpha << 24) | (mTextColor & 0xFFFFFF); 147 mPrevText.setTextColor(transparentColor); 148 mNextText.setTextColor(transparentColor); 149 } 150 151 /** 152 * Set the default text size to a given unit and value. 153 * See {@link TypedValue} for the possible dimension units. 154 * 155 * <p>Example: to set the text size to 14px, use 156 * setTextSize(TypedValue.COMPLEX_UNIT_PX, 14);</p> 157 * 158 * @param unit The desired dimension unit 159 * @param size The desired size in the given units 160 */ 161 public void setTextSize(int unit, float size) { 162 mPrevText.setTextSize(unit, size); 163 mCurrText.setTextSize(unit, size); 164 mNextText.setTextSize(unit, size); 165 } 166 167 @Override 168 protected void onAttachedToWindow() { 169 super.onAttachedToWindow(); 170 171 final ViewParent parent = getParent(); 172 if (!(parent instanceof ViewPager)) { 173 throw new IllegalStateException( 174 "PagerTitleStrip must be a direct child of a ViewPager."); 175 } 176 177 final ViewPager pager = (ViewPager) parent; 178 final PagerAdapter adapter = pager.getAdapter(); 179 180 pager.setInternalPageChangeListener(mPageListener); 181 pager.setOnAdapterChangeListener(mPageListener); 182 mPager = pager; 183 updateAdapter(null, adapter); 184 } 185 186 @Override 187 protected void onDetachedFromWindow() { 188 updateAdapter(mPager.getAdapter(), null); 189 mPager.setInternalPageChangeListener(null); 190 mPager.setOnAdapterChangeListener(null); 191 mPager = null; 192 } 193 194 void updateText(int currentItem, PagerAdapter adapter) { 195 final int itemCount = adapter != null ? adapter.getCount() : 0; 196 mUpdatingText = true; 197 198 CharSequence text = null; 199 if (currentItem >= 1 && adapter != null) { 200 text = adapter.getPageTitle(currentItem - 1); 201 } 202 mPrevText.setText(text); 203 204 mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null); 205 206 text = null; 207 if (currentItem + 1 < itemCount && adapter != null) { 208 text = adapter.getPageTitle(currentItem + 1); 209 } 210 mNextText.setText(text); 211 212 // Measure everything 213 final int width = getWidth() - getPaddingLeft() - getPaddingRight(); 214 final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 215 final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f), 216 MeasureSpec.AT_MOST); 217 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); 218 mPrevText.measure(childWidthSpec, childHeightSpec); 219 mCurrText.measure(childWidthSpec, childHeightSpec); 220 mNextText.measure(childWidthSpec, childHeightSpec); 221 222 mLastKnownCurrentPage = currentItem; 223 224 if (!mUpdatingPositions) { 225 updateTextPositions(currentItem, mLastKnownPositionOffset); 226 } 227 228 mUpdatingText = false; 229 } 230 231 @Override 232 public void requestLayout() { 233 if (!mUpdatingText) { 234 super.requestLayout(); 235 } 236 } 237 238 void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) { 239 if (oldAdapter != null) { 240 oldAdapter.unregisterDataSetObserver(mPageListener); 241 } 242 if (newAdapter != null) { 243 newAdapter.registerDataSetObserver(mPageListener); 244 } 245 if (mPager != null) { 246 mLastKnownCurrentPage = -1; 247 mLastKnownPositionOffset = -1; 248 updateText(mPager.getCurrentItem(), newAdapter); 249 requestLayout(); 250 } 251 } 252 253 void updateTextPositions(int position, float positionOffset) { 254 if (position != mLastKnownCurrentPage) { 255 updateText(position, mPager.getAdapter()); 256 } else if (positionOffset == mLastKnownPositionOffset) { 257 return; 258 } 259 260 mUpdatingPositions = true; 261 262 final int prevWidth = mPrevText.getMeasuredWidth(); 263 final int currWidth = mCurrText.getMeasuredWidth(); 264 final int nextWidth = mNextText.getMeasuredWidth(); 265 final int halfCurrWidth = currWidth / 2; 266 267 final int stripWidth = getWidth(); 268 final int paddingLeft = getPaddingLeft(); 269 final int paddingRight = getPaddingRight(); 270 final int paddingTop = getPaddingTop(); 271 final int textPaddedLeft = paddingLeft + halfCurrWidth; 272 final int textPaddedRight = paddingRight + halfCurrWidth; 273 final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight; 274 275 float currOffset = positionOffset + 0.5f; 276 if (currOffset > 1.f) { 277 currOffset -= 1.f; 278 } 279 final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset); 280 final int currLeft = currCenter - currWidth / 2; 281 final int currRight = currLeft + currWidth; 282 283 mCurrText.layout(currLeft, paddingTop, currRight, 284 paddingTop + mCurrText.getMeasuredHeight()); 285 286 final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth); 287 mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth, 288 paddingTop + mPrevText.getMeasuredHeight()); 289 290 final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth, 291 currRight + mScaledTextSpacing); 292 mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth, 293 paddingTop + mNextText.getMeasuredHeight()); 294 295 mLastKnownPositionOffset = positionOffset; 296 mUpdatingPositions = false; 297 } 298 299 @Override 300 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 301 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 302 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 303 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 304 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 305 306 if (widthMode != MeasureSpec.EXACTLY) { 307 throw new IllegalStateException("Must measure with an exact width"); 308 } 309 310 int childHeight = heightSize; 311 int minHeight = 0; 312 int padding = 0; 313 final Drawable bg = getBackground(); 314 if (bg != null) { 315 minHeight = bg.getIntrinsicHeight(); 316 } 317 padding = getPaddingTop() + getPaddingBottom(); 318 childHeight -= padding; 319 320 final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f), 321 MeasureSpec.AT_MOST); 322 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode); 323 324 mPrevText.measure(childWidthSpec, childHeightSpec); 325 mCurrText.measure(childWidthSpec, childHeightSpec); 326 mNextText.measure(childWidthSpec, childHeightSpec); 327 328 if (heightMode == MeasureSpec.EXACTLY) { 329 setMeasuredDimension(widthSize, heightSize); 330 } else { 331 int textHeight = mCurrText.getMeasuredHeight(); 332 setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding)); 333 } 334 } 335 336 @Override 337 protected void onLayout(boolean changed, int l, int t, int r, int b) { 338 if (mPager != null) { 339 updateTextPositions(mPager.getCurrentItem(), 0.f); 340 } 341 } 342 343 private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener, 344 ViewPager.OnAdapterChangeListener { 345 private int mScrollState; 346 347 @Override 348 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 349 if (positionOffset > 0.5f) { 350 // Consider ourselves to be on the next page when we're 50% of the way there. 351 position++; 352 } 353 updateTextPositions(position, positionOffset); 354 } 355 356 @Override 357 public void onPageSelected(int position) { 358 if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 359 // Only update the text here if we're not dragging or settling. 360 updateText(mPager.getCurrentItem(), mPager.getAdapter()); 361 } 362 } 363 364 @Override 365 public void onPageScrollStateChanged(int state) { 366 mScrollState = state; 367 } 368 369 @Override 370 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) { 371 updateAdapter(oldAdapter, newAdapter); 372 } 373 374 @Override 375 public void onChanged() { 376 updateText(mPager.getCurrentItem(), mPager.getAdapter()); 377 } 378 } 379} 380