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