PagerTitleStrip.java revision 8fffe01871be1806a1bdefa1f7213b660fcf5ac0
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 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 { 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 mFirstLayout = true; 53 private boolean mUpdatingText; 54 private boolean mUpdatingPositions; 55 56 private final PageListener mPageListener = new PageListener(); 57 58 private static final int[] ATTRS = new int[] { 59 android.R.attr.textAppearance, 60 android.R.attr.textColor, 61 android.R.attr.textSize 62 }; 63 64 private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 = invisible, FF = opaque 65 private static final int TEXT_SPACING = 16; // dip 66 67 public PagerTitleStrip(Context context) { 68 this(context, null); 69 } 70 71 public PagerTitleStrip(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 74 addView(mPrevText = new TextView(context)); 75 addView(mCurrText = new TextView(context)); 76 addView(mNextText = new TextView(context)); 77 78 final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); 79 final int textAppearance = a.getResourceId(0, 0); 80 if (textAppearance != 0) { 81 mPrevText.setTextAppearance(context, textAppearance); 82 mCurrText.setTextAppearance(context, textAppearance); 83 mNextText.setTextAppearance(context, textAppearance); 84 } 85 if (a.hasValue(1)) { 86 final int textColor = a.getColor(1, 0); 87 mPrevText.setTextColor(textColor); 88 mCurrText.setTextColor(textColor); 89 mNextText.setTextColor(textColor); 90 } 91 final int textSize = a.getDimensionPixelSize(2, 0); 92 if (textSize != 0) { 93 mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 94 mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 95 mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 96 } 97 a.recycle(); 98 99 final int defaultColor = mPrevText.getTextColors().getDefaultColor(); 100 final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF); 101 mPrevText.setTextColor(transparentColor); 102 mNextText.setTextColor(transparentColor); 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 @Override 116 protected void onAttachedToWindow() { 117 super.onAttachedToWindow(); 118 119 final ViewParent parent = getParent(); 120 if (!(parent instanceof ViewPager)) { 121 throw new IllegalStateException( 122 "PagerTitleStrip must be a direct child of a ViewPager."); 123 } 124 125 final ViewPager pager = (ViewPager) parent; 126 final PagerAdapter adapter = pager.getAdapter(); 127 128 pager.setInternalPageChangeListener(mPageListener); 129 pager.setOnAdapterChangeListener(mPageListener); 130 mPager = pager; 131 updateAdapter(null, adapter); 132 } 133 134 @Override 135 protected void onDetachedFromWindow() { 136 updateAdapter(mPager.getAdapter(), null); 137 mPager.setInternalPageChangeListener(null); 138 mPager.setOnAdapterChangeListener(null); 139 mPager = null; 140 } 141 142 void updateText(int currentItem, PagerAdapter adapter) { 143 final int itemCount = adapter != null ? adapter.getCount() : 0; 144 mUpdatingText = true; 145 146 CharSequence text = null; 147 if (currentItem >= 1 && adapter != null) { 148 text = adapter.getPageTitle(currentItem - 1); 149 } 150 mPrevText.setText(text); 151 152 mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null); 153 154 text = null; 155 if (currentItem + 1 < itemCount && adapter != null) { 156 text = adapter.getPageTitle(currentItem + 1); 157 } 158 mNextText.setText(text); 159 160 // Measure everything 161 final int width = getWidth() - getPaddingLeft() - getPaddingRight(); 162 final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 163 final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f), 164 MeasureSpec.AT_MOST); 165 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); 166 mPrevText.measure(childWidthSpec, childHeightSpec); 167 mCurrText.measure(childWidthSpec, childHeightSpec); 168 mNextText.measure(childWidthSpec, childHeightSpec); 169 170 mLastKnownCurrentPage = currentItem; 171 172 if (!mUpdatingPositions) { 173 updateTextPositions(currentItem, mLastKnownPositionOffset); 174 } 175 176 mUpdatingText = false; 177 } 178 179 @Override 180 public void requestLayout() { 181 if (!mUpdatingText) { 182 super.requestLayout(); 183 } 184 } 185 186 void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) { 187 if (oldAdapter != null) { 188 oldAdapter.unregisterDataSetObserver(mPageListener); 189 } 190 if (newAdapter != null) { 191 newAdapter.registerDataSetObserver(mPageListener); 192 } 193 if (mPager != null) { 194 mLastKnownCurrentPage = -1; 195 mLastKnownPositionOffset = -1; 196 updateText(mPager.getCurrentItem(), newAdapter); 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