java.lang.Object
↳ViewGroup
↳androidx.viewpager.widget.PagerTitleStrip
Subclasses:
PagerTabStrip
Gradle dependencies
compile group: 'androidx.viewpager', name: 'viewpager', version: '1.1.0-alpha01'
- groupId: androidx.viewpager
- artifactId: viewpager
- version: 1.1.0-alpha01
Artifact androidx.viewpager:viewpager:1.1.0-alpha01 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.viewpager:viewpager com.android.support:viewpager
Androidx class mapping:
androidx.viewpager.widget.PagerTitleStrip android.support.v4.view.PagerTitleStrip
Overview
PagerTitleStrip is a non-interactive indicator of the current, next,
and previous pages of a 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
PagerAdapter.getPageTitle(int) in the adapter supplied to
the ViewPager.
For an interactive indicator, see PagerTabStrip.
Summary
Methods |
---|
public int | getTextSpacing()
|
protected void | onAttachedToWindow()
|
protected void | onDetachedFromWindow()
|
protected void | onLayout(boolean changed, int l, int t, int r, int b)
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
public void | requestLayout()
|
public void | setGravity(int gravity)
Set the used to position text within the title strip. |
public void | setNonPrimaryAlpha(float alpha)
Set the alpha value used for non-primary page titles. |
public void | setTextColor(int color)
Set the color value used as the base color for all displayed page titles. |
public void | setTextSize(int unit, float size)
Set the default text size to a given unit and value. |
public void | setTextSpacing(int spacingPixels)
Set the required spacing between title segments. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
PagerTitleStrip(Context context)
public
PagerTitleStrip(Context context, AttributeSet attrs)
Methods
public void
setTextSpacing(int spacingPixels)
Set the required spacing between title segments.
Parameters:
spacingPixels: Spacing between each title displayed in pixels
public int
getTextSpacing()
Returns:
The required spacing between title segments in pixels
public void
setNonPrimaryAlpha(float alpha)
Set the alpha value used for non-primary page titles.
Parameters:
alpha: Opacity value in the range 0-1f
public void
setTextColor(int color)
Set the color value used as the base color for all displayed page titles.
Alpha will be ignored for non-primary page titles. See PagerTitleStrip.setNonPrimaryAlpha(float).
Parameters:
color: Color hex code in 0xAARRGGBB format
public void
setTextSize(int unit, float size)
Set the default text size to a given unit and value.
See for the possible dimension units.
Example: to set the text size to 14px, use
setTextSize(TypedValue.COMPLEX_UNIT_PX, 14);
Parameters:
unit: The desired dimension unit
size: The desired size in the given units
public void
setGravity(int gravity)
Set the used to position text within the title strip.
Only the vertical gravity component is used.
Parameters:
gravity: constant for positioning title text
protected void
onAttachedToWindow()
protected void
onDetachedFromWindow()
public void
requestLayout()
protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void
onLayout(boolean changed, int l, int t, int r, int b)
Source
/*
* Copyright 2018 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 androidx.viewpager.widget;
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.text.method.SingleLineTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.core.widget.TextViewCompat;
import java.lang.ref.WeakReference;
import java.util.Locale;
/**
* 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.
*
* <p>For an interactive indicator, see {@link PagerTabStrip}.</p>
*/
@ViewPager.DecorView
public class PagerTitleStrip extends ViewGroup {
ViewPager mPager;
TextView mPrevText;
TextView mCurrText;
TextView mNextText;
private int mLastKnownCurrentPage = -1;
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;
private static class SingleLineAllCapsTransform extends SingleLineTransformationMethod {
private Locale mLocale;
@SuppressWarnings("deprecation")
SingleLineAllCapsTransform(Context context) {
mLocale = context.getResources().getConfiguration().locale;
}
@Override
public CharSequence getTransformation(CharSequence source, View view) {
source = super.getTransformation(source, view);
return source != null ? source.toString().toUpperCase(mLocale) : null;
}
}
private static void setSingleLineAllCaps(TextView text) {
text.setTransformationMethod(new SingleLineAllCapsTransform(text.getContext()));
}
public PagerTitleStrip(@NonNull Context context) {
this(context, null);
}
public PagerTitleStrip(@NonNull Context context, @Nullable 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);
ViewCompat.saveAttributeDataForStyleable(this, context, ATTRS, attrs, a, 0, 0);
final int textAppearance = a.getResourceId(0, 0);
if (textAppearance != 0) {
TextViewCompat.setTextAppearance(mPrevText, textAppearance);
TextViewCompat.setTextAppearance(mCurrText, textAppearance);
TextViewCompat.setTextAppearance(mNextText, 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(@FloatRange(from = 0.0, to = 1.0) 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(@ColorInt 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.addOnAdapterChangeListener(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.removeOnAdapterChangeListener(mPageListener);
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 maxWidth = Math.max(0, (int) (width * 0.8f));
final int childWidthSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int maxHeight = Math.max(0, childHeight);
final int childHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, 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);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Must measure with an exact width");
}
final int heightPadding = getPaddingTop() + getPaddingBottom();
final int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
heightPadding, LayoutParams.WRAP_CONTENT);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int widthPadding = (int) (widthSize * 0.2f);
final int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,
widthPadding, LayoutParams.WRAP_CONTENT);
mPrevText.measure(childWidthSpec, childHeightSpec);
mCurrText.measure(childWidthSpec, childHeightSpec);
mNextText.measure(childWidthSpec, childHeightSpec);
final int height;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else {
final int textHeight = mCurrText.getMeasuredHeight();
final int minHeight = getMinHeight();
height = Math.max(minHeight, textHeight + heightPadding);
}
final int childState = mCurrText.getMeasuredState();
final int measuredHeight = View.resolveSizeAndState(height, heightMeasureSpec,
childState << View.MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(widthSize, measuredHeight);
}
@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;
PageListener() {
}
@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(ViewPager viewPager, 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);
}
}
}