PagerTitleStrip.java revision 0574ca37da4619afe4e26753f5a1b4de314b6565
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 public PagerTitleStrip(Context context) { 67 this(context, null); 68 } 69 70 public PagerTitleStrip(Context context, AttributeSet attrs) { 71 super(context, attrs); 72 73 addView(mPrevText = new TextView(context)); 74 addView(mCurrText = new TextView(context)); 75 addView(mNextText = new TextView(context)); 76 77 final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); 78 final int textAppearance = a.getResourceId(0, 0); 79 if (textAppearance != 0) { 80 mPrevText.setTextAppearance(context, textAppearance); 81 mCurrText.setTextAppearance(context, textAppearance); 82 mNextText.setTextAppearance(context, textAppearance); 83 } 84 if (a.hasValue(1)) { 85 final int textColor = a.getColor(1, 0); 86 mPrevText.setTextColor(textColor); 87 mCurrText.setTextColor(textColor); 88 mNextText.setTextColor(textColor); 89 } 90 final int textSize = a.getDimensionPixelSize(2, 0); 91 if (textSize != 0) { 92 mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 93 mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 94 mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 95 } 96 a.recycle(); 97 98 final int defaultColor = mPrevText.getTextColors().getDefaultColor(); 99 final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF); 100 mPrevText.setTextColor(transparentColor); 101 mNextText.setTextColor(transparentColor); 102 103 mPrevText.setEllipsize(TruncateAt.END); 104 mCurrText.setEllipsize(TruncateAt.END); 105 mNextText.setEllipsize(TruncateAt.END); 106 mPrevText.setSingleLine(); 107 mCurrText.setSingleLine(); 108 mNextText.setSingleLine(); 109 110 final float density = context.getResources().getDisplayMetrics().density; 111 mScaledTextSpacing = (int) (TEXT_SPACING * density); 112 } 113 114 @Override 115 protected void onAttachedToWindow() { 116 super.onAttachedToWindow(); 117 118 final ViewParent parent = getParent(); 119 if (!(parent instanceof ViewPager)) { 120 throw new IllegalStateException( 121 "PagerTitleStrip must be a direct child of a ViewPager."); 122 } 123 124 final ViewPager pager = (ViewPager) parent; 125 final PagerAdapter adapter = pager.getAdapter(); 126 127 pager.setInternalPageChangeListener(mPageListener); 128 pager.setOnAdapterChangeListener(mPageListener); 129 mPager = pager; 130 updateAdapter(null, adapter); 131 } 132 133 @Override 134 protected void onDetachedFromWindow() { 135 updateAdapter(mPager.getAdapter(), null); 136 mPager.setInternalPageChangeListener(null); 137 mPager.setOnAdapterChangeListener(null); 138 mPager = null; 139 } 140 141 void updateText(int currentItem, PagerAdapter adapter) { 142 final int itemCount = adapter != null ? adapter.getCount() : 0; 143 mUpdatingText = true; 144 145 CharSequence text = null; 146 if (currentItem >= 1 && adapter != null) { 147 text = adapter.getPageTitle(currentItem - 1); 148 } 149 mPrevText.setText(text); 150 151 mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null); 152 153 text = null; 154 if (currentItem + 1 < itemCount && adapter != null) { 155 text = adapter.getPageTitle(currentItem + 1); 156 } 157 mNextText.setText(text); 158 159 // Measure everything 160 final int width = getWidth() - getPaddingLeft() - getPaddingRight(); 161 final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 162 final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f), 163 MeasureSpec.AT_MOST); 164 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); 165 mPrevText.measure(childWidthSpec, childHeightSpec); 166 mCurrText.measure(childWidthSpec, childHeightSpec); 167 mNextText.measure(childWidthSpec, childHeightSpec); 168 169 mLastKnownCurrentPage = currentItem; 170 171 if (!mUpdatingPositions) { 172 updateTextPositions(currentItem, mLastKnownPositionOffset); 173 } 174 175 mUpdatingText = false; 176 } 177 178 @Override 179 public void requestLayout() { 180 if (!mUpdatingText) { 181 super.requestLayout(); 182 } 183 } 184 185 void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) { 186 if (oldAdapter != null) { 187 oldAdapter.unregisterDataSetObserver(mPageListener); 188 } 189 if (newAdapter != null) { 190 newAdapter.registerDataSetObserver(mPageListener); 191 } 192 if (mPager != null) { 193 mLastKnownCurrentPage = -1; 194 mLastKnownPositionOffset = -1; 195 updateText(mPager.getCurrentItem(), newAdapter); 196 requestLayout(); 197 } 198 } 199 200 void updateTextPositions(int position, float positionOffset) { 201 if (position != mLastKnownCurrentPage) { 202 updateText(position, mPager.getAdapter()); 203 } else if (positionOffset == mLastKnownPositionOffset) { 204 return; 205 } 206 207 mUpdatingPositions = true; 208 209 final int prevWidth = mPrevText.getMeasuredWidth(); 210 final int currWidth = mCurrText.getMeasuredWidth(); 211 final int nextWidth = mNextText.getMeasuredWidth(); 212 final int halfCurrWidth = currWidth / 2; 213 214 final int stripWidth = getWidth(); 215 final int paddingLeft = getPaddingLeft(); 216 final int paddingRight = getPaddingRight(); 217 final int paddingTop = getPaddingTop(); 218 final int textPaddedLeft = paddingLeft + halfCurrWidth; 219 final int textPaddedRight = paddingRight + halfCurrWidth; 220 final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight; 221 222 float currOffset = positionOffset + 0.5f; 223 if (currOffset > 1.f) { 224 currOffset -= 1.f; 225 } 226 final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset); 227 final int currLeft = currCenter - currWidth / 2; 228 final int currRight = currLeft + currWidth; 229 230 mCurrText.layout(currLeft, paddingTop, currRight, 231 paddingTop + mCurrText.getMeasuredHeight()); 232 233 final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth); 234 mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth, 235 paddingTop + mPrevText.getMeasuredHeight()); 236 237 final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth, 238 currRight + mScaledTextSpacing); 239 mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth, 240 paddingTop + mNextText.getMeasuredHeight()); 241 242 mLastKnownPositionOffset = positionOffset; 243 mUpdatingPositions = false; 244 } 245 246 @Override 247 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 248 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 249 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 250 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 251 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 252 253 if (widthMode != MeasureSpec.EXACTLY) { 254 throw new IllegalStateException("Must measure with an exact width"); 255 } 256 257 int childHeight = heightSize; 258 int minHeight = 0; 259 int padding = 0; 260 final Drawable bg = getBackground(); 261 if (bg != null) { 262 minHeight = bg.getIntrinsicHeight(); 263 } 264 padding = getPaddingTop() + getPaddingBottom(); 265 childHeight -= padding; 266 267 final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f), 268 MeasureSpec.AT_MOST); 269 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode); 270 271 mPrevText.measure(childWidthSpec, childHeightSpec); 272 mCurrText.measure(childWidthSpec, childHeightSpec); 273 mNextText.measure(childWidthSpec, childHeightSpec); 274 275 if (heightMode == MeasureSpec.EXACTLY) { 276 setMeasuredDimension(widthSize, heightSize); 277 } else { 278 int textHeight = mCurrText.getMeasuredHeight(); 279 setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding)); 280 } 281 } 282 283 @Override 284 protected void onLayout(boolean changed, int l, int t, int r, int b) { 285 if (mPager != null) { 286 updateTextPositions(mPager.getCurrentItem(), 0.f); 287 } 288 } 289 290 private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener, 291 ViewPager.OnAdapterChangeListener { 292 private int mScrollState; 293 294 @Override 295 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 296 if (positionOffset > 0.5f) { 297 // Consider ourselves to be on the next page when we're 50% of the way there. 298 position++; 299 } 300 updateTextPositions(position, positionOffset); 301 } 302 303 @Override 304 public void onPageSelected(int position) { 305 if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 306 // Only update the text here if we're not dragging or settling. 307 updateText(mPager.getCurrentItem(), mPager.getAdapter()); 308 } 309 } 310 311 @Override 312 public void onPageScrollStateChanged(int state) { 313 mScrollState = state; 314 } 315 316 @Override 317 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) { 318 updateAdapter(oldAdapter, newAdapter); 319 } 320 321 @Override 322 public void onChanged() { 323 updateText(mPager.getCurrentItem(), mPager.getAdapter()); 324 } 325 } 326} 327