如何为水平回收视图制作页面指示器
How to make a page indicator for horizontal recyclerview
知道如何为 recyclerview 列表创建页面指示器吗?
首先,您必须为圆圈创建另一个 RecyclerView,并将此代码放在第一个 RecyclerView 的 OnScrollListener 中。
int[] firstVisibleItemPositions = new int[appList.size()];
int[] lastVisibleItemPositions = new int[appList.size()];
int position = ((StaggeredGridLayoutManager) listView.getLayoutManager()).findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
View currentView = circlesList.getChildAt(position);
int lastPosition = ((StaggeredGridLayoutManager) listView.getLayoutManager()).findLastVisibleItemPositions(lastVisibleItemPositions)[0];
View lastView = circlesList.getChildAt(lastPosition);
ImageView circle;
if (dx>0) {
if (currentView != null && lastPosition != position) {
circle = (ImageView) currentView.findViewById(R.id.img);
circle.setImageResource(R.drawable.empty_circle);
}
if (lastView != null && lastPosition != position) {
circle = (ImageView) lastView.findViewById(R.id.img);
circle.setImageResource(R.drawable.selected_circle);
}
}else if (dx<0){
if (currentView != null && lastPosition != position) {
circle = (ImageView) currentView.findViewById(R.id.img);
circle.setImageResource(R.drawable.selected_circle);
}
if (lastView != null && lastPosition != position) {
circle = (ImageView) lastView.findViewById(R.id.img);
circle.setImageResource(R.drawable.empty_circle);
}
}
您可以使用 RecyclerView.ItemDecoration 添加指标。
只需在底部画一些线或圆圈,然后使用layoutManager.findFirstVisibleItemPosition()
即可获得当前活动项目。由于寻呼机往往会填满整个宽度,因此这是获取显示项目的准确方法。这也允许我们通过比较孩子的左边缘与 parents.
来计算滚动距离
下面是一个示例装饰,它绘制了一些线条并在它们之间设置了动画
public class LinePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xFFFFFFFF;
private int colorInactive = 0x66FFFFFF;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public LinePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// draw the line for every item
c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress;
// draw the cut off highlight
c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
// draw the highlight overlapping to the next item as well
if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
这会给你如下的结果
还有一个博客 post 更详细地介绍了装饰的工作原理 here and the full source code is available at GitHub
我复制了 David Medenjak 给出的相同答案,但要在 recyclerview 下方画圈。我已经更新了上面答案中的几行代码,请查看并相应使用。
/**
* Created by shobhan on 4/10/17.
*/
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0x727272;
private int colorInactive = 0xF44336;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(Color.GRAY);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// draw the line for every item
c.drawCircle(start + mIndicatorItemLength,indicatorPosY,itemWidth/6,mPaint);
// c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(Color.RED);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
/* c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
*/
c.drawCircle(highlightStart,indicatorPosY,itemWidth/6,mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress;
c.drawCircle(highlightStart + mIndicatorItemLength,indicatorPosY,itemWidth/6,mPaint);
// draw the cut off highlight
/* c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
*/
// draw the highlight overlapping to the next item as well
/* if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
*//*c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);*//*
c.drawCircle(highlightStart ,indicatorPosY,itemWidth/4,mPaint);
}*/
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
并将其应用到 recyclerview 如下
//for horizontal scroll for recycler view
LinearLayoutManager linearLayoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
recyclerview.setLayoutManager(linearLayoutManager);
recyclerview.addItemDecoration(new CirclePagerIndicatorDecoration());
我已经更改了圈子的代码。删除了绘制线的代码,并用绘制圆的方法替换了相同的代码。
请在下面找到完整的 class:
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xDE000000;
private int colorInactive = 0x33000000;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 4;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 4;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 8;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
int right = activeChild.getRight();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawCircle(highlightStart, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress + mIndicatorItemPadding*progress;
c.drawCircle(highlightStart + partialLength, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
非常感谢,
LB古普塔
快乐编码!!!!!
如果有人需要,我为此编写了自己的库(经过大量搜索):RecyclerView indicator。操作方法如下:
<com.kingfisher.easyviewindicator.RecyclerViewIndicator
android:id="@+id/circleIndicator"
android:layout_width="match_parent"
android:layout_height="20dp"
app:avi_animation_enable="true"
app:avi_drawable="@drawable/blue_radius"
app:avi_drawable_unselected="@drawable/gray_radius"
app:avi_height="10dp"
app:avi_margin="10dp"
app:avi_width="10dp"
app:layout_constraintTop_toBottomOf="@+id/recyclerView">
</com.kingfisher.easyviewindicator.RecyclerViewIndicator>
// In code:
recyclerView.setAdapter(new TestAdapter());
recyclerViewIndicator.setRecyclerView(recyclerView);
现在您可以使用 ViewPager2。
它基本上包装了一个 RecyclerView
(因此您将使用 RecyclerView.Adapter
),但它允许在 TabLayoutMediator 的帮助下附加一个 TabLayout
。
然后可以将 TabLayout
的样式设置为类似于点页面指示器。例如参见 [=16=]
实际上有一个非常好的库可以用于此目的。
只需将其添加到您的 gradle 依赖项
implementation "ru.tinkoff.scrollingpagerindicator:scrollingpagerindicator:1.0.6"
然后添加布局
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ru.tinkoff.scrollingpagerindicator.ScrollingPagerIndicator
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
然后像这样将指标添加到您的回收站视图
RecyclerView recyclerView = findViewById(R.id.recycler);
LayoutManager layoutManager = new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
DemoRecyclerViewAdapter recyclerAdapter = new DemoRecyclerViewAdapter();
recyclerView.setAdapter(recyclerAdapter);
ScrollingPagerIndicator recyclerIndicator = findViewById(R.id.indicator);
recyclerIndicator.attachToRecyclerView(recyclerView);
您可以将 PageIndicator.java class 添加到您的 .xml below recycler view widget
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_white">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_notice_board_cards"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_high_lights" />
<com.abc.widget.PageIndicator
android:id="@+id/ll_image_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginStart="@dimen/dimen_16dp"
android:layout_marginTop="@dimen/dimen_12dp"
android:layout_marginEnd="@dimen/dimen_16dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_cards_count"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_notice_board_cards"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
像下面的PageIndicator.javaclass
public class PageIndicator extends LinearLayout {
private ImageView[] imageIndications;
public PageIndicator(Context context) {
super(context);
}
public PageIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* method to create the pageIndicator
*/
public void createPageIndicator(int pageCount, int focusedPageDrawable,
int unFocusedPageDrawable) {
imageIndications = new ImageView[pageCount];
ImageView indicatorImageView;
for (int i = 0; i < pageCount; i++) {
indicatorImageView = new ImageView(getContext());
int size = BaseUtils.INSTANCE.getDensityPixelValue(getContext(), 8);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
params.setMargins(8, 0, 4, 0);
indicatorImageView.setLayoutParams(params);
// method to change the page icon
changePageIcon(i, 0, indicatorImageView, focusedPageDrawable, unFocusedPageDrawable);
imageIndications[i] = indicatorImageView;
this.addView(indicatorImageView);
}
}
/**
* method to handle the PageChangeListener for ViewPager
*
* @param size the total number of images available for product
* @param position the current position of ViewPager
* @param focusedPageDrawable
* @param unFocusedPageDrawable
*/
public void handleViewPagerScroll(int size, int position, int focusedPageDrawable,
int unFocusedPageDrawable) {
for (int i = 0; i < size && i < imageIndications.length; i++) {
changePageIcon(position, i, imageIndications[i], focusedPageDrawable, unFocusedPageDrawable);
imageIndications[i].getLayoutParams().width = imageIndications[i].getDrawable().getIntrinsicWidth();
}
}
/**
* method to change the page icon
*
* @param position
* @param indicatorImageView
* @param focusedPageDrawable
* @param unFocusedPageDrawable
*/
private void changePageIcon(int position, int pageIndex, ImageView indicatorImageView,
int focusedPageDrawable, int unFocusedPageDrawable) {
if (pageIndex == position) {
if (focusedPageDrawable != 0) {
indicatorImageView.setImageResource(focusedPageDrawable);
} else {
indicatorImageView.setImageResource(R.drawable.rounded_style_blue);
}
} else {
if (unFocusedPageDrawable != 0) {
indicatorImageView.setImageResource(unFocusedPageDrawable);
} else {
indicatorImageView.setImageResource(R.drawable.rounded_style2);
}
}
}
}
在你的回收视图适配器中,你可以在下面添加覆盖接口
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is LinearLayoutManager && itemCount > 0) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visiblePosition: Int = manager.findFirstCompletelyVisibleItemPosition()
if (visiblePosition > -1) {
iFragmentCommunicator.updateCount(visiblePosition)
}
}
})
}
}
interface IFragmentCommunicator {
fun updateCount(count: Int)
}
最后在您的 Activity 或 Fragment 中,您最初可以添加以下代码来调用 Page Indicator 方法。
private fun initCircularIndicator() {
val margin =
(screenWidth - (0.8F * screenWidth).toInt()) / 2 - 8)
(mBinder?.llImageIndicator?.layoutParams as? FrameLayout.LayoutParams)?.apply {
setMargins(margin, 0, 0, 32))
}
mBinder?.llImageIndicator?.requestLayout()
mBinder?.llImageIndicator?.run {
removeAllViews()
createPageIndicator(
8,
R.drawable.selected_item_indicator,
0
)
handleViewPagerScroll(8, 0, R.drawable.selected_blue_item_indicator, 0)
}
}
在上面的代码中,您可以为 selected_blue_item_indicator 添加可绘制对象,如下所示
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/color_brand" />
<corners android:radius="@dimen/four_dp" />
<size
android:width="@dimen/sixteen_dp"
android:height="@dimen/ten_dp" />
</shape>
并且一旦您重写 Activity 中的 updateCount() 方法或片段调用 Page Indicator
的 handleViewPagerScroll() 方法
override fun updateCount(count: Int) {
mBinder?.llImageIndicator?.handleViewPagerScroll(
8,
count,
R.drawable.selected_blue_item_indicator,
0
)
}
这就是您要做的全部。
我对 CirclePagerIndicatorDecoration 进行了调整,因此它将支持 RTL (Right-to-Left) 语言。花了几天时间,希望对大家有帮助:
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.annotation.ColorInt;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Locale;
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xDE000000;
private int colorInactive = 0x33000000;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 4;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 4;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 8;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration(@ColorInt int colorInactive) {
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
colorActive = colorInactive;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition;
if (isRtlLanguage()) {
activePosition = layoutManager.findLastVisibleItemPosition();
} else {
activePosition = layoutManager.findFirstVisibleItemPosition();
}
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
int right = activeChild.getRight();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
if (isRtlLanguage()) {
indicatorStartX = (parent.getWidth() + indicatorTotalWidth) / 2F - (mIndicatorItemLength + DP * 4) / 2;
}
// float indicatorStartXhl = (parent.getWidth() + indicatorTotalWidth) / 2F - (mIndicatorItemLength + DP * 4) / 2;
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart;
if (isRtlLanguage()) {
highlightStart = indicatorStartX - itemWidth * highlightPosition;
} else {
highlightStart = indicatorStartX + itemWidth * highlightPosition;
}
c.drawCircle(highlightStart, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
} else {
float highlightStart;
if (isRtlLanguage()) {
highlightStart = indicatorStartX - itemWidth * highlightPosition;
} else {
highlightStart = indicatorStartX + itemWidth * highlightPosition;
}
float partialLength = mIndicatorItemLength * progress + mIndicatorItemPadding * progress;
c.drawCircle(highlightStart + partialLength, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
//The method that checks if it's RTL language:
private boolean isRtlLanguage() {
String deviceLanguage = Locale.getDefault().getLanguage();
return (deviceLanguage.contains("iw") || deviceLanguage.contains("ar")); //You can change here to your specific language
}
Shoban 的回答对我不起作用,所以这就是我如何让它起作用的。
请记住,我删除了动画,因为它们对于我的用例来说不是必需的。结果看起来和这个问题的截图一模一样。
此外,为了允许一次只滚动一个元素,我使用了 PagerSnapHelper() 来达到预期的结果。
PagerSnapHelper helper = new PagerSnapHelper();
helper.attachToRecyclerView(recyclerView);
recyclerView.addItemDecoration(new CirclePagerIndicatorDecoration());
这是我的装饰器代码:
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private final int colorActive = 0xFFFFFFFF;
private final int colorInactive = 0x66FFFFFF;
private final int circleRadius = 8;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, circleRadius, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
//width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
if (progress == 0F) {
// no swipe, draw a normal indicator
c.drawCircle(highlightStart, indicatorPosY, circleRadius, mPaint);
}
}
@Override
public void getItemOffsets(@NotNull Rect outRect,
@NotNull View view,
@NotNull RecyclerView parent,
@NotNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
万一有人在寻找 Xamarin.Android 解决方案,这是一个基于以上答案的解决方案:
public class RecyclerViewPageIndicator: RecyclerView.ItemDecoration
{
private readonly int size = ConvertDpToPixels(8);
private readonly int spacing = ConvertDpToPixels(10);
private readonly AccelerateDecelerateInterpolator interpolator;
private readonly Paint paint;
public RecyclerViewPageIndicator()
{
interpolator = new AccelerateDecelerateInterpolator();
paint = new Paint
{
AntiAlias = true,
StrokeCap = Paint.Cap.Round,
StrokeWidth = ConvertDpToPixels(2)
};
paint.SetStyle(Paint.Style.Fill);
}
public override void OnDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
{
base.OnDrawOver(c, parent, state);
var itemCount = parent.GetAdapter()?.ItemCount ?? 0;
var totalWidth = size * itemCount;
var totalSpacingWidth = Math.Max(0, itemCount - 1) * spacing;
var indicatorWidth = totalWidth + totalSpacingWidth;
var indicatorStartX = (parent.Width - indicatorWidth + size) / 2f;
var indicatorPosY = parent.Height - size * 2;
DrawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
var layoutManager = (LinearLayoutManager)parent.GetLayoutManager();
var position = layoutManager.FindFirstVisibleItemPosition();
if (position == RecyclerView.NoPosition)
{
return;
}
var activeChild = layoutManager.FindViewByPosition(position);
var progress = interpolator.GetInterpolation(activeChild.Left * -1 / (float)activeChild.Width);
DrawHighlights(c, indicatorStartX, indicatorPosY, position, progress);
}
private void DrawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount)
{
paint.Color = Color.ParseColor("#D2E7F6");
var itemWidth = size + spacing;
var drawPosition = indicatorStartX;
for (var i = 0; i < itemCount; i++)
{
c.DrawCircle(drawPosition, indicatorPosY, size / 2f, paint);
drawPosition += itemWidth;
}
}
private void DrawHighlights(Canvas c, float indicatorStartX, float indicatorPosY, int position, float progress)
{
paint.Color = Color.ParseColor("#007AFF");
var itemWidth = size + spacing;
if (progress == 0)
{
var highlightStart = indicatorStartX + itemWidth * position;
c.DrawCircle(highlightStart, indicatorPosY, size / 2f, paint);
}
else
{
var highlightStart = indicatorStartX + itemWidth * position;
var partialLength = size * progress + spacing * progress;
c.DrawCircle(highlightStart + partialLength, indicatorPosY, size / 2f, paint);
}
}
public override void GetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
{
base.GetItemOffsets(outRect, view, parent, state);
outRect.Bottom = size * 3;
}
private static int ConvertDpToPixels(int dpSize)
{
var scale = Resources.System.DisplayMetrics.Density;
return (int)(dpSize * scale + 0.5f);
}
}
知道如何为 recyclerview 列表创建页面指示器吗?
首先,您必须为圆圈创建另一个 RecyclerView,并将此代码放在第一个 RecyclerView 的 OnScrollListener 中。
int[] firstVisibleItemPositions = new int[appList.size()];
int[] lastVisibleItemPositions = new int[appList.size()];
int position = ((StaggeredGridLayoutManager) listView.getLayoutManager()).findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
View currentView = circlesList.getChildAt(position);
int lastPosition = ((StaggeredGridLayoutManager) listView.getLayoutManager()).findLastVisibleItemPositions(lastVisibleItemPositions)[0];
View lastView = circlesList.getChildAt(lastPosition);
ImageView circle;
if (dx>0) {
if (currentView != null && lastPosition != position) {
circle = (ImageView) currentView.findViewById(R.id.img);
circle.setImageResource(R.drawable.empty_circle);
}
if (lastView != null && lastPosition != position) {
circle = (ImageView) lastView.findViewById(R.id.img);
circle.setImageResource(R.drawable.selected_circle);
}
}else if (dx<0){
if (currentView != null && lastPosition != position) {
circle = (ImageView) currentView.findViewById(R.id.img);
circle.setImageResource(R.drawable.selected_circle);
}
if (lastView != null && lastPosition != position) {
circle = (ImageView) lastView.findViewById(R.id.img);
circle.setImageResource(R.drawable.empty_circle);
}
}
您可以使用 RecyclerView.ItemDecoration 添加指标。
只需在底部画一些线或圆圈,然后使用layoutManager.findFirstVisibleItemPosition()
即可获得当前活动项目。由于寻呼机往往会填满整个宽度,因此这是获取显示项目的准确方法。这也允许我们通过比较孩子的左边缘与 parents.
下面是一个示例装饰,它绘制了一些线条并在它们之间设置了动画
public class LinePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xFFFFFFFF;
private int colorInactive = 0x66FFFFFF;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public LinePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// draw the line for every item
c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress;
// draw the cut off highlight
c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
// draw the highlight overlapping to the next item as well
if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
这会给你如下的结果
还有一个博客 post 更详细地介绍了装饰的工作原理 here and the full source code is available at GitHub
我复制了 David Medenjak 给出的相同答案,但要在 recyclerview 下方画圈。我已经更新了上面答案中的几行代码,请查看并相应使用。
/**
* Created by shobhan on 4/10/17.
*/
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0x727272;
private int colorInactive = 0xF44336;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(Color.GRAY);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// draw the line for every item
c.drawCircle(start + mIndicatorItemLength,indicatorPosY,itemWidth/6,mPaint);
// c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(Color.RED);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
/* c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
*/
c.drawCircle(highlightStart,indicatorPosY,itemWidth/6,mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress;
c.drawCircle(highlightStart + mIndicatorItemLength,indicatorPosY,itemWidth/6,mPaint);
// draw the cut off highlight
/* c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
*/
// draw the highlight overlapping to the next item as well
/* if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
*//*c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);*//*
c.drawCircle(highlightStart ,indicatorPosY,itemWidth/4,mPaint);
}*/
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
并将其应用到 recyclerview 如下
//for horizontal scroll for recycler view
LinearLayoutManager linearLayoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
recyclerview.setLayoutManager(linearLayoutManager);
recyclerview.addItemDecoration(new CirclePagerIndicatorDecoration());
我已经更改了圈子的代码。删除了绘制线的代码,并用绘制圆的方法替换了相同的代码。 请在下面找到完整的 class:
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xDE000000;
private int colorInactive = 0x33000000;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 4;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 4;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 8;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
int right = activeChild.getRight();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawCircle(highlightStart, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress + mIndicatorItemPadding*progress;
c.drawCircle(highlightStart + partialLength, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
非常感谢, LB古普塔 快乐编码!!!!!
如果有人需要,我为此编写了自己的库(经过大量搜索):RecyclerView indicator。操作方法如下:
<com.kingfisher.easyviewindicator.RecyclerViewIndicator
android:id="@+id/circleIndicator"
android:layout_width="match_parent"
android:layout_height="20dp"
app:avi_animation_enable="true"
app:avi_drawable="@drawable/blue_radius"
app:avi_drawable_unselected="@drawable/gray_radius"
app:avi_height="10dp"
app:avi_margin="10dp"
app:avi_width="10dp"
app:layout_constraintTop_toBottomOf="@+id/recyclerView">
</com.kingfisher.easyviewindicator.RecyclerViewIndicator>
// In code:
recyclerView.setAdapter(new TestAdapter());
recyclerViewIndicator.setRecyclerView(recyclerView);
现在您可以使用 ViewPager2。
它基本上包装了一个 RecyclerView
(因此您将使用 RecyclerView.Adapter
),但它允许在 TabLayoutMediator 的帮助下附加一个 TabLayout
。
然后可以将 TabLayout
的样式设置为类似于点页面指示器。例如参见 [=16=]
实际上有一个非常好的库可以用于此目的。 只需将其添加到您的 gradle 依赖项
implementation "ru.tinkoff.scrollingpagerindicator:scrollingpagerindicator:1.0.6"
然后添加布局
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ru.tinkoff.scrollingpagerindicator.ScrollingPagerIndicator
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
然后像这样将指标添加到您的回收站视图
RecyclerView recyclerView = findViewById(R.id.recycler);
LayoutManager layoutManager = new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
DemoRecyclerViewAdapter recyclerAdapter = new DemoRecyclerViewAdapter();
recyclerView.setAdapter(recyclerAdapter);
ScrollingPagerIndicator recyclerIndicator = findViewById(R.id.indicator);
recyclerIndicator.attachToRecyclerView(recyclerView);
您可以将 PageIndicator.java class 添加到您的 .xml below recycler view widget
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_white">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_notice_board_cards"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_high_lights" />
<com.abc.widget.PageIndicator
android:id="@+id/ll_image_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginStart="@dimen/dimen_16dp"
android:layout_marginTop="@dimen/dimen_12dp"
android:layout_marginEnd="@dimen/dimen_16dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_cards_count"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_notice_board_cards"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
像下面的PageIndicator.javaclass
public class PageIndicator extends LinearLayout {
private ImageView[] imageIndications;
public PageIndicator(Context context) {
super(context);
}
public PageIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* method to create the pageIndicator
*/
public void createPageIndicator(int pageCount, int focusedPageDrawable,
int unFocusedPageDrawable) {
imageIndications = new ImageView[pageCount];
ImageView indicatorImageView;
for (int i = 0; i < pageCount; i++) {
indicatorImageView = new ImageView(getContext());
int size = BaseUtils.INSTANCE.getDensityPixelValue(getContext(), 8);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
params.setMargins(8, 0, 4, 0);
indicatorImageView.setLayoutParams(params);
// method to change the page icon
changePageIcon(i, 0, indicatorImageView, focusedPageDrawable, unFocusedPageDrawable);
imageIndications[i] = indicatorImageView;
this.addView(indicatorImageView);
}
}
/**
* method to handle the PageChangeListener for ViewPager
*
* @param size the total number of images available for product
* @param position the current position of ViewPager
* @param focusedPageDrawable
* @param unFocusedPageDrawable
*/
public void handleViewPagerScroll(int size, int position, int focusedPageDrawable,
int unFocusedPageDrawable) {
for (int i = 0; i < size && i < imageIndications.length; i++) {
changePageIcon(position, i, imageIndications[i], focusedPageDrawable, unFocusedPageDrawable);
imageIndications[i].getLayoutParams().width = imageIndications[i].getDrawable().getIntrinsicWidth();
}
}
/**
* method to change the page icon
*
* @param position
* @param indicatorImageView
* @param focusedPageDrawable
* @param unFocusedPageDrawable
*/
private void changePageIcon(int position, int pageIndex, ImageView indicatorImageView,
int focusedPageDrawable, int unFocusedPageDrawable) {
if (pageIndex == position) {
if (focusedPageDrawable != 0) {
indicatorImageView.setImageResource(focusedPageDrawable);
} else {
indicatorImageView.setImageResource(R.drawable.rounded_style_blue);
}
} else {
if (unFocusedPageDrawable != 0) {
indicatorImageView.setImageResource(unFocusedPageDrawable);
} else {
indicatorImageView.setImageResource(R.drawable.rounded_style2);
}
}
}
}
在你的回收视图适配器中,你可以在下面添加覆盖接口
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is LinearLayoutManager && itemCount > 0) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visiblePosition: Int = manager.findFirstCompletelyVisibleItemPosition()
if (visiblePosition > -1) {
iFragmentCommunicator.updateCount(visiblePosition)
}
}
})
}
}
interface IFragmentCommunicator {
fun updateCount(count: Int)
}
最后在您的 Activity 或 Fragment 中,您最初可以添加以下代码来调用 Page Indicator 方法。
private fun initCircularIndicator() {
val margin =
(screenWidth - (0.8F * screenWidth).toInt()) / 2 - 8)
(mBinder?.llImageIndicator?.layoutParams as? FrameLayout.LayoutParams)?.apply {
setMargins(margin, 0, 0, 32))
}
mBinder?.llImageIndicator?.requestLayout()
mBinder?.llImageIndicator?.run {
removeAllViews()
createPageIndicator(
8,
R.drawable.selected_item_indicator,
0
)
handleViewPagerScroll(8, 0, R.drawable.selected_blue_item_indicator, 0)
}
}
在上面的代码中,您可以为 selected_blue_item_indicator 添加可绘制对象,如下所示
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/color_brand" />
<corners android:radius="@dimen/four_dp" />
<size
android:width="@dimen/sixteen_dp"
android:height="@dimen/ten_dp" />
</shape>
并且一旦您重写 Activity 中的 updateCount() 方法或片段调用 Page Indicator
的 handleViewPagerScroll() 方法override fun updateCount(count: Int) {
mBinder?.llImageIndicator?.handleViewPagerScroll(
8,
count,
R.drawable.selected_blue_item_indicator,
0
)
}
这就是您要做的全部。
我对 CirclePagerIndicatorDecoration 进行了调整,因此它将支持 RTL (Right-to-Left) 语言。花了几天时间,希望对大家有帮助:
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.annotation.ColorInt;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Locale;
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xDE000000;
private int colorInactive = 0x33000000;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 4;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 4;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 8;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration(@ColorInt int colorInactive) {
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
colorActive = colorInactive;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition;
if (isRtlLanguage()) {
activePosition = layoutManager.findLastVisibleItemPosition();
} else {
activePosition = layoutManager.findFirstVisibleItemPosition();
}
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
int right = activeChild.getRight();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
if (isRtlLanguage()) {
indicatorStartX = (parent.getWidth() + indicatorTotalWidth) / 2F - (mIndicatorItemLength + DP * 4) / 2;
}
// float indicatorStartXhl = (parent.getWidth() + indicatorTotalWidth) / 2F - (mIndicatorItemLength + DP * 4) / 2;
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart;
if (isRtlLanguage()) {
highlightStart = indicatorStartX - itemWidth * highlightPosition;
} else {
highlightStart = indicatorStartX + itemWidth * highlightPosition;
}
c.drawCircle(highlightStart, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
} else {
float highlightStart;
if (isRtlLanguage()) {
highlightStart = indicatorStartX - itemWidth * highlightPosition;
} else {
highlightStart = indicatorStartX + itemWidth * highlightPosition;
}
float partialLength = mIndicatorItemLength * progress + mIndicatorItemPadding * progress;
c.drawCircle(highlightStart + partialLength, indicatorPosY, mIndicatorItemLength / 2F, mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
//The method that checks if it's RTL language:
private boolean isRtlLanguage() {
String deviceLanguage = Locale.getDefault().getLanguage();
return (deviceLanguage.contains("iw") || deviceLanguage.contains("ar")); //You can change here to your specific language
}
Shoban 的回答对我不起作用,所以这就是我如何让它起作用的。
请记住,我删除了动画,因为它们对于我的用例来说不是必需的。结果看起来和这个问题的截图一模一样。
此外,为了允许一次只滚动一个元素,我使用了 PagerSnapHelper() 来达到预期的结果。
PagerSnapHelper helper = new PagerSnapHelper();
helper.attachToRecyclerView(recyclerView);
recyclerView.addItemDecoration(new CirclePagerIndicatorDecoration());
这是我的装饰器代码:
public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private final int colorActive = 0xFFFFFFFF;
private final int colorInactive = 0x66FFFFFF;
private final int circleRadius = 8;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public CirclePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, circleRadius, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
//width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
if (progress == 0F) {
// no swipe, draw a normal indicator
c.drawCircle(highlightStart, indicatorPosY, circleRadius, mPaint);
}
}
@Override
public void getItemOffsets(@NotNull Rect outRect,
@NotNull View view,
@NotNull RecyclerView parent,
@NotNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
万一有人在寻找 Xamarin.Android 解决方案,这是一个基于以上答案的解决方案:
public class RecyclerViewPageIndicator: RecyclerView.ItemDecoration
{
private readonly int size = ConvertDpToPixels(8);
private readonly int spacing = ConvertDpToPixels(10);
private readonly AccelerateDecelerateInterpolator interpolator;
private readonly Paint paint;
public RecyclerViewPageIndicator()
{
interpolator = new AccelerateDecelerateInterpolator();
paint = new Paint
{
AntiAlias = true,
StrokeCap = Paint.Cap.Round,
StrokeWidth = ConvertDpToPixels(2)
};
paint.SetStyle(Paint.Style.Fill);
}
public override void OnDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
{
base.OnDrawOver(c, parent, state);
var itemCount = parent.GetAdapter()?.ItemCount ?? 0;
var totalWidth = size * itemCount;
var totalSpacingWidth = Math.Max(0, itemCount - 1) * spacing;
var indicatorWidth = totalWidth + totalSpacingWidth;
var indicatorStartX = (parent.Width - indicatorWidth + size) / 2f;
var indicatorPosY = parent.Height - size * 2;
DrawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
var layoutManager = (LinearLayoutManager)parent.GetLayoutManager();
var position = layoutManager.FindFirstVisibleItemPosition();
if (position == RecyclerView.NoPosition)
{
return;
}
var activeChild = layoutManager.FindViewByPosition(position);
var progress = interpolator.GetInterpolation(activeChild.Left * -1 / (float)activeChild.Width);
DrawHighlights(c, indicatorStartX, indicatorPosY, position, progress);
}
private void DrawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount)
{
paint.Color = Color.ParseColor("#D2E7F6");
var itemWidth = size + spacing;
var drawPosition = indicatorStartX;
for (var i = 0; i < itemCount; i++)
{
c.DrawCircle(drawPosition, indicatorPosY, size / 2f, paint);
drawPosition += itemWidth;
}
}
private void DrawHighlights(Canvas c, float indicatorStartX, float indicatorPosY, int position, float progress)
{
paint.Color = Color.ParseColor("#007AFF");
var itemWidth = size + spacing;
if (progress == 0)
{
var highlightStart = indicatorStartX + itemWidth * position;
c.DrawCircle(highlightStart, indicatorPosY, size / 2f, paint);
}
else
{
var highlightStart = indicatorStartX + itemWidth * position;
var partialLength = size * progress + spacing * progress;
c.DrawCircle(highlightStart + partialLength, indicatorPosY, size / 2f, paint);
}
}
public override void GetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
{
base.GetItemOffsets(outRect, view, parent, state);
outRect.Bottom = size * 3;
}
private static int ConvertDpToPixels(int dpSize)
{
var scale = Resources.System.DisplayMetrics.Density;
return (int)(dpSize * scale + 0.5f);
}
}