Created
September 3, 2015 14:51
-
-
Save sveinungkb/38a18f3c6f6bc45a1ebc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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.Gravity; | |
import android.view.ViewGroup; | |
import android.view.ViewParent; | |
import android.widget.TextView; | |
import java.lang.ref.WeakReference; | |
/** | |
* 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 android.support.v4.view.PagerAdapter#getPageTitle(int)} in the adapter supplied to | |
* the ViewPager. | |
* | |
* <p>For an interactive indicator, see {@link android.support.v4.view.PagerTabStrip}.</p> | |
*/ | |
/** | |
* Since v23 of the support library contain a layout bug we include the v22 of these files. | |
* https://code.google.com/p/android/issues/detail?id=184715 | |
*/ | |
public class PagerTitleStripV22 extends ViewGroup implements ViewPager.Decor { | |
private static final String TAG = "PagerTitleStrip"; | |
ViewPager mPager; | |
TextView mPrevText; | |
TextView mCurrText; | |
TextView mNextText; | |
private int mLastKnownCurrentPage = -1; | |
private float mLastKnownPositionOffset = -1; | |
private int mScaledTextSpacing; | |
private int mGravity; | |
private boolean mUpdatingText; | |
private boolean mUpdatingPositions; | |
private final PageListener mPageListener = new PageListener(); | |
private WeakReference<PagerAdapter> mWatchingAdapter; | |
private static final int[] ATTRS = new int[] { | |
android.R.attr.textAppearance, | |
android.R.attr.textSize, | |
android.R.attr.textColor, | |
android.R.attr.gravity | |
}; | |
private static final int[] TEXT_ATTRS = new int[] { | |
0x0101038c // android.R.attr.textAllCaps | |
}; | |
private static final float SIDE_ALPHA = 0.6f; | |
private static final int TEXT_SPACING = 16; // dip | |
private int mNonPrimaryAlpha; | |
int mTextColor; | |
interface PagerTitleStripImpl { | |
void setSingleLineAllCaps(TextView text); | |
} | |
static class PagerTitleStripImplBase implements PagerTitleStripImpl { | |
public void setSingleLineAllCaps(TextView text) { | |
text.setSingleLine(); | |
} | |
} | |
static class PagerTitleStripImplIcs implements PagerTitleStripImpl { | |
public void setSingleLineAllCaps(TextView text) { | |
PagerTitleStripIcs.setSingleLineAllCaps(text); | |
} | |
} | |
private static final PagerTitleStripImpl IMPL; | |
static { | |
if (android.os.Build.VERSION.SDK_INT >= 14) { | |
IMPL = new PagerTitleStripImplIcs(); | |
} else { | |
IMPL = new PagerTitleStripImplBase(); | |
} | |
} | |
private static void setSingleLineAllCaps(TextView text) { | |
IMPL.setSingleLineAllCaps(text); | |
} | |
public PagerTitleStripV22(Context context) { | |
this(context, null); | |
} | |
public PagerTitleStripV22(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); | |
} | |
final int textSize = a.getDimensionPixelSize(1, 0); | |
if (textSize != 0) { | |
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); | |
} | |
if (a.hasValue(2)) { | |
final int textColor = a.getColor(2, 0); | |
mPrevText.setTextColor(textColor); | |
mCurrText.setTextColor(textColor); | |
mNextText.setTextColor(textColor); | |
} | |
mGravity = a.getInteger(3, Gravity.BOTTOM); | |
a.recycle(); | |
mTextColor = mCurrText.getTextColors().getDefaultColor(); | |
setNonPrimaryAlpha(SIDE_ALPHA); | |
mPrevText.setEllipsize(TruncateAt.END); | |
mCurrText.setEllipsize(TruncateAt.END); | |
mNextText.setEllipsize(TruncateAt.END); | |
boolean allCaps = false; | |
if (textAppearance != 0) { | |
final TypedArray ta = context.obtainStyledAttributes(textAppearance, TEXT_ATTRS); | |
allCaps = ta.getBoolean(0, false); | |
ta.recycle(); | |
} | |
if (allCaps) { | |
setSingleLineAllCaps(mPrevText); | |
setSingleLineAllCaps(mCurrText); | |
setSingleLineAllCaps(mNextText); | |
} else { | |
mPrevText.setSingleLine(); | |
mCurrText.setSingleLine(); | |
mNextText.setSingleLine(); | |
} | |
final float density = context.getResources().getDisplayMetrics().density; | |
mScaledTextSpacing = (int) (TEXT_SPACING * density); | |
} | |
/** | |
* Set the required spacing between title segments. | |
* | |
* @param spacingPixels Spacing between each title displayed in pixels | |
*/ | |
public void setTextSpacing(int spacingPixels) { | |
mScaledTextSpacing = spacingPixels; | |
requestLayout(); | |
} | |
/** | |
* @return The required spacing between title segments in pixels | |
*/ | |
public int getTextSpacing() { | |
return mScaledTextSpacing; | |
} | |
/** | |
* Set the alpha value used for non-primary page titles. | |
* | |
* @param alpha Opacity value in the range 0-1f | |
*/ | |
public void setNonPrimaryAlpha(float alpha) { | |
mNonPrimaryAlpha = (int) (alpha * 255) & 0xFF; | |
final int transparentColor = (mNonPrimaryAlpha << 24) | (mTextColor & 0xFFFFFF); | |
mPrevText.setTextColor(transparentColor); | |
mNextText.setTextColor(transparentColor); | |
} | |
/** | |
* Set the color value used as the base color for all displayed page titles. | |
* Alpha will be ignored for non-primary page titles. See {@link #setNonPrimaryAlpha(float)}. | |
* | |
* @param color Color hex code in 0xAARRGGBB format | |
*/ | |
public void setTextColor(int color) { | |
mTextColor = color; | |
mCurrText.setTextColor(color); | |
final int transparentColor = (mNonPrimaryAlpha << 24) | (mTextColor & 0xFFFFFF); | |
mPrevText.setTextColor(transparentColor); | |
mNextText.setTextColor(transparentColor); | |
} | |
/** | |
* Set the default text size to a given unit and value. | |
* See {@link TypedValue} for the possible dimension units. | |
* | |
* <p>Example: to set the text size to 14px, use | |
* setTextSize(TypedValue.COMPLEX_UNIT_PX, 14);</p> | |
* | |
* @param unit The desired dimension unit | |
* @param size The desired size in the given units | |
*/ | |
public void setTextSize(int unit, float size) { | |
mPrevText.setTextSize(unit, size); | |
mCurrText.setTextSize(unit, size); | |
mNextText.setTextSize(unit, size); | |
} | |
/** | |
* Set the {@link Gravity} used to position text within the title strip. | |
* Only the vertical gravity component is used. | |
* | |
* @param gravity {@link Gravity} constant for positioning title text | |
*/ | |
public void setGravity(int gravity) { | |
mGravity = gravity; | |
requestLayout(); | |
} | |
@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(mWatchingAdapter != null ? mWatchingAdapter.get() : null, adapter); | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
super.onDetachedFromWindow(); | |
if (mPager != null) { | |
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 && currentItem < itemCount ? | |
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.AT_MOST); | |
mPrevText.measure(childWidthSpec, childHeightSpec); | |
mCurrText.measure(childWidthSpec, childHeightSpec); | |
mNextText.measure(childWidthSpec, childHeightSpec); | |
mLastKnownCurrentPage = currentItem; | |
if (!mUpdatingPositions) { | |
updateTextPositions(currentItem, mLastKnownPositionOffset, false); | |
} | |
mUpdatingText = false; | |
} | |
@Override | |
public void requestLayout() { | |
if (!mUpdatingText) { | |
super.requestLayout(); | |
} | |
} | |
void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) { | |
if (oldAdapter != null) { | |
oldAdapter.unregisterDataSetObserver(mPageListener); | |
mWatchingAdapter = null; | |
} | |
if (newAdapter != null) { | |
newAdapter.registerDataSetObserver(mPageListener); | |
mWatchingAdapter = new WeakReference<PagerAdapter>(newAdapter); | |
} | |
if (mPager != null) { | |
mLastKnownCurrentPage = -1; | |
mLastKnownPositionOffset = -1; | |
updateText(mPager.getCurrentItem(), newAdapter); | |
requestLayout(); | |
} | |
} | |
void updateTextPositions(int position, float positionOffset, boolean force) { | |
if (position != mLastKnownCurrentPage) { | |
updateText(position, mPager.getAdapter()); | |
} else if (!force && 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 stripHeight = getHeight(); | |
final int paddingLeft = getPaddingLeft(); | |
final int paddingRight = getPaddingRight(); | |
final int paddingTop = getPaddingTop(); | |
final int paddingBottom = getPaddingBottom(); | |
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; | |
final int prevBaseline = mPrevText.getBaseline(); | |
final int currBaseline = mCurrText.getBaseline(); | |
final int nextBaseline = mNextText.getBaseline(); | |
final int maxBaseline = Math.max(Math.max(prevBaseline, currBaseline), nextBaseline); | |
final int prevTopOffset = maxBaseline - prevBaseline; | |
final int currTopOffset = maxBaseline - currBaseline; | |
final int nextTopOffset = maxBaseline - nextBaseline; | |
final int alignedPrevHeight = prevTopOffset + mPrevText.getMeasuredHeight(); | |
final int alignedCurrHeight = currTopOffset + mCurrText.getMeasuredHeight(); | |
final int alignedNextHeight = nextTopOffset + mNextText.getMeasuredHeight(); | |
final int maxTextHeight = Math.max(Math.max(alignedPrevHeight, alignedCurrHeight), | |
alignedNextHeight); | |
final int vgrav = mGravity & Gravity.VERTICAL_GRAVITY_MASK; | |
int prevTop; | |
int currTop; | |
int nextTop; | |
switch (vgrav) { | |
default: | |
case Gravity.TOP: | |
prevTop = paddingTop + prevTopOffset; | |
currTop = paddingTop + currTopOffset; | |
nextTop = paddingTop + nextTopOffset; | |
break; | |
case Gravity.CENTER_VERTICAL: | |
final int paddedHeight = stripHeight - paddingTop - paddingBottom; | |
final int centeredTop = (paddedHeight - maxTextHeight) / 2; | |
prevTop = centeredTop + prevTopOffset; | |
currTop = centeredTop + currTopOffset; | |
nextTop = centeredTop + nextTopOffset; | |
break; | |
case Gravity.BOTTOM: | |
final int bottomGravTop = stripHeight - paddingBottom - maxTextHeight; | |
prevTop = bottomGravTop + prevTopOffset; | |
currTop = bottomGravTop + currTopOffset; | |
nextTop = bottomGravTop + nextTopOffset; | |
break; | |
} | |
mCurrText.layout(currLeft, currTop, currRight, | |
currTop + mCurrText.getMeasuredHeight()); | |
final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth); | |
mPrevText.layout(prevLeft, prevTop, prevLeft + prevWidth, | |
prevTop + mPrevText.getMeasuredHeight()); | |
final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth, | |
currRight + mScaledTextSpacing); | |
mNextText.layout(nextLeft, nextTop, nextLeft + nextWidth, | |
nextTop + 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 = getMinHeight(); | |
int padding = 0; | |
padding = getPaddingTop() + getPaddingBottom(); | |
childHeight -= padding; | |
final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f), | |
MeasureSpec.AT_MOST); | |
final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST); | |
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) { | |
final float offset = mLastKnownPositionOffset >= 0 ? mLastKnownPositionOffset : 0; | |
updateTextPositions(mLastKnownCurrentPage, offset, true); | |
} | |
} | |
int getMinHeight() { | |
int minHeight = 0; | |
final Drawable bg = getBackground(); | |
if (bg != null) { | |
minHeight = bg.getIntrinsicHeight(); | |
} | |
return minHeight; | |
} | |
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, false); | |
} | |
@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()); | |
final float offset = mLastKnownPositionOffset >= 0 ? mLastKnownPositionOffset : 0; | |
updateTextPositions(mPager.getCurrentItem(), offset, true); | |
} | |
} | |
@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()); | |
final float offset = mLastKnownPositionOffset >= 0 ? mLastKnownPositionOffset : 0; | |
updateTextPositions(mPager.getCurrentItem(), offset, true); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment