您可以使用RecyclerView.ItemDecoration添加指示器。
只需在底部绘制一些线条或圆圈,然后使用layoutManager.findFirstVisibleItemPosition()
来获取当前活动的项目。由于页面通常填充整个宽度,因此这是获取显示项目的准确方式。这也使我们能够通过比较子元素的左侧边缘和父元素来计算滚动距离。
下面是一个示例装饰品,绘制了一些线条并在它们之间进行动画处理。
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;
}
}
这将给您一个类似下面的结果:
还有一篇博客文章详细介绍了这种装饰是如何工作的,点击这里。完整的源代码 可在GitHub上获得。
getItemOffsets(..)
声明了装饰所使用的空间。如果你想要在顶部绘制,那么你需要设置 top,或者什么都不设置(如果你想要在内容上方绘制)。如果你想要将其放置在顶部,你需要修改 indicatorPosY
并删除高度调用(只需使用 mIndicatorHeight / 2F
即可)。 - David Medenjak我已经修改了圆形的代码。删除了画线的代码,并使用绘制圆形的方法进行替换。 下面是完整的类:
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 Gupta
祝编码愉快!!!!
我复制了 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());
如果有人需要,我自己写了一个库(在经过多次搜索后): RecyclerView指示器 。使用方法如下:
<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);
我对CirclePagerIndicatorDecoration进行了调整,以支持RTL(从右到左)语言。 这花费了我几天时间,希望能帮助到某些人:
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
}
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 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);
如果有人正在寻找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);
}
}
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;
}
RecyclerView
(因此你将使用RecyclerView.Adapter
),但是借助TabLayoutMediator的帮助,它允许附加TabLayout
。然后,TabLayout
可以被设计成像点页指示器一样。例如,请参见How do you create an Android View Pager with a dots indicator?。
<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.java类如下所示
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中,您可以最后添加以下代码来最初调用页面指示器方法。
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)
}
}
<?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或Fragment中覆盖了updateCount()方法,请调用Page Indicator的handleViewPagerScroll()方法。
override fun updateCount(count: Int) {
mBinder?.llImageIndicator?.handleViewPagerScroll(
8,
count,
R.drawable.selected_blue_item_indicator,
0
)
}