/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.view; import android.content.Context; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.TypedValue; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.TextView; /** * PagerTitleStrip is a non-interactive indicator of the current, next, * and previous pages of a {@link ViewPager}. It is intended to be used as a * child view of a ViewPager widget in your XML layout. * Add it as a child of a ViewPager in your layout file and set its * android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom * of the ViewPager. The title from each page is supplied by the method * {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to * the ViewPager. */ public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor { private static final String TAG = "PagerTitleStrip"; ViewPager mPager; private TextView mPrevText; private TextView mCurrText; private TextView mNextText; private int mLastKnownCurrentPage = -1; private float mLastKnownPositionOffset = -1; private int mScaledTextSpacing; private boolean mUpdatingText; private boolean mUpdatingPositions; private final PageListener mPageListener = new PageListener(); private static final int[] ATTRS = new int[] { android.R.attr.textAppearance, android.R.attr.textColor, android.R.attr.textSize }; private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 = invisible, FF = opaque private static final int TEXT_SPACING = 16; // dip public PagerTitleStrip(Context context) { this(context, null); } public PagerTitleStrip(Context context, AttributeSet attrs) { super(context, attrs); addView(mPrevText = new TextView(context)); addView(mCurrText = new TextView(context)); addView(mNextText = new TextView(context)); final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); final int textAppearance = a.getResourceId(0, 0); if (textAppearance != 0) { mPrevText.setTextAppearance(context, textAppearance); mCurrText.setTextAppearance(context, textAppearance); mNextText.setTextAppearance(context, textAppearance); } if (a.hasValue(1)) { final int textColor = a.getColor(1, 0); mPrevText.setTextColor(textColor); mCurrText.setTextColor(textColor); mNextText.setTextColor(textColor); } final int textSize = a.getDimensionPixelSize(2, 0); if (textSize != 0) { mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } a.recycle(); final int defaultColor = mPrevText.getTextColors().getDefaultColor(); final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF); mPrevText.setTextColor(transparentColor); mNextText.setTextColor(transparentColor); mPrevText.setEllipsize(TruncateAt.END); mCurrText.setEllipsize(TruncateAt.END); mNextText.setEllipsize(TruncateAt.END); mPrevText.setSingleLine(); mCurrText.setSingleLine(); mNextText.setSingleLine(); final float density = context.getResources().getDisplayMetrics().density; mScaledTextSpacing = (int) (TEXT_SPACING * density); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); final ViewParent parent = getParent(); if (!(parent instanceof ViewPager)) { throw new IllegalStateException( "PagerTitleStrip must be a direct child of a ViewPager."); } final ViewPager pager = (ViewPager) parent; final PagerAdapter adapter = pager.getAdapter(); pager.setInternalPageChangeListener(mPageListener); pager.setOnAdapterChangeListener(mPageListener); mPager = pager; updateAdapter(null, adapter); } @Override protected void onDetachedFromWindow() { updateAdapter(mPager.getAdapter(), null); mPager.setInternalPageChangeListener(null); mPager.setOnAdapterChangeListener(null); mPager = null; } void updateText(int currentItem, PagerAdapter adapter) { final int itemCount = adapter != null ? adapter.getCount() : 0; mUpdatingText = true; CharSequence text = null; if (currentItem >= 1 && adapter != null) { text = adapter.getPageTitle(currentItem - 1); } mPrevText.setText(text); mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null); text = null; if (currentItem + 1 < itemCount && adapter != null) { text = adapter.getPageTitle(currentItem + 1); } mNextText.setText(text); // Measure everything final int width = getWidth() - getPaddingLeft() - getPaddingRight(); final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom(); final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f), MeasureSpec.AT_MOST); final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); mPrevText.measure(childWidthSpec, childHeightSpec); mCurrText.measure(childWidthSpec, childHeightSpec); mNextText.measure(childWidthSpec, childHeightSpec); mLastKnownCurrentPage = currentItem; if (!mUpdatingPositions) { updateTextPositions(currentItem, mLastKnownPositionOffset); } mUpdatingText = false; } @Override public void requestLayout() { if (!mUpdatingText) { super.requestLayout(); } } void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) { if (oldAdapter != null) { oldAdapter.unregisterDataSetObserver(mPageListener); } if (newAdapter != null) { newAdapter.registerDataSetObserver(mPageListener); } if (mPager != null) { mLastKnownCurrentPage = -1; mLastKnownPositionOffset = -1; updateText(mPager.getCurrentItem(), newAdapter); requestLayout(); } } void updateTextPositions(int position, float positionOffset) { if (position != mLastKnownCurrentPage) { updateText(position, mPager.getAdapter()); } else if (positionOffset == mLastKnownPositionOffset) { return; } mUpdatingPositions = true; final int prevWidth = mPrevText.getMeasuredWidth(); final int currWidth = mCurrText.getMeasuredWidth(); final int nextWidth = mNextText.getMeasuredWidth(); final int halfCurrWidth = currWidth / 2; final int stripWidth = getWidth(); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int textPaddedLeft = paddingLeft + halfCurrWidth; final int textPaddedRight = paddingRight + halfCurrWidth; final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight; float currOffset = positionOffset + 0.5f; if (currOffset > 1.f) { currOffset -= 1.f; } final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset); final int currLeft = currCenter - currWidth / 2; final int currRight = currLeft + currWidth; mCurrText.layout(currLeft, paddingTop, currRight, paddingTop + mCurrText.getMeasuredHeight()); final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth); mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth, paddingTop + mPrevText.getMeasuredHeight()); final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth, currRight + mScaledTextSpacing); mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth, paddingTop + mNextText.getMeasuredHeight()); mLastKnownPositionOffset = positionOffset; mUpdatingPositions = false; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); final int widthSize = MeasureSpec.getSize(widthMeasureSpec); final int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { throw new IllegalStateException("Must measure with an exact width"); } int childHeight = heightSize; int minHeight = 0; int padding = 0; final Drawable bg = getBackground(); if (bg != null) { minHeight = bg.getIntrinsicHeight(); } padding = getPaddingTop() + getPaddingBottom(); childHeight -= padding; final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f), MeasureSpec.AT_MOST); final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode); mPrevText.measure(childWidthSpec, childHeightSpec); mCurrText.measure(childWidthSpec, childHeightSpec); mNextText.measure(childWidthSpec, childHeightSpec); if (heightMode == MeasureSpec.EXACTLY) { setMeasuredDimension(widthSize, heightSize); } else { int textHeight = mCurrText.getMeasuredHeight(); setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding)); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mPager != null) { updateTextPositions(mPager.getCurrentItem(), 0.f); } } private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener, ViewPager.OnAdapterChangeListener { private int mScrollState; @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (positionOffset > 0.5f) { // Consider ourselves to be on the next page when we're 50% of the way there. position++; } updateTextPositions(position, positionOffset); } @Override public void onPageSelected(int position) { if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { // Only update the text here if we're not dragging or settling. updateText(mPager.getCurrentItem(), mPager.getAdapter()); } } @Override public void onPageScrollStateChanged(int state) { mScrollState = state; } @Override public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) { updateAdapter(oldAdapter, newAdapter); } @Override public void onChanged() { updateText(mPager.getCurrentItem(), mPager.getAdapter()); } } }