如何为水平回收视图制作页面指示器

时间:2015-11-21 08:34:00

标签: android android-layout android-recyclerview recyclerview-layout

Any idea how to add page indicator

知道如何为recyclerview列表创建页面指示器吗?

11 个答案:

答案 0 :(得分:27)

您可以使用RecyclerView.ItemDecoration添加指标。

只需在底部绘制一些线条或圆圈,然后使用class OptionButton extends Component { constructor(props) { super(props) this.state = { selected: false } } unselectSet() { this.setState({ selected: false }) } selectSet() { this.setState({ selected: true }) } toggler() { this.state.selected ? this.unselectSet() : this.selectSet() } render() { return ( <div> <button onClick={() => this.toggler()} style={numberButtonStyle}>{this.props.opt}</button> </div> ) } } 获取当前有效项目。由于寻呼机倾向于填充整个宽度,因此这是获取显示项目的累积方式。这也允许我们通过比较孩子的左边缘和父母来计算滚动距离。

下面是一个样本装饰,它在它们之间绘制一些线条和动画

layoutManager.findFirstVisibleItemPosition()

这将为您提供如下结果

pager indicator

还有一篇博文更详细地介绍了装饰的工作原理here,完整的源代码是available at GitHub

答案 1 :(得分:18)

我更改了圈子的代码。删除了绘制线的代码,并将其替换为绘制圆方法。 请在下面找到完整的课程:

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古普塔 快乐编码!!!!!

答案 2 :(得分:4)

我复制了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());

答案 3 :(得分:4)

如果有人需要,我为此编写了自己的库(经过大量搜索):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);

答案 4 :(得分:1)

您可以在Recycler视图小部件下将PageIndicator.java类添加到.xml中

<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)
  }

最后在“活动”或“片段”中,您可以首先添加以下代码以调用“页面指示器”方法。

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或Fragment中重写了updateCount()方法或调用页面指示器的handleViewPagerScroll()方法

override fun updateCount(count: Int) {
    mBinder?.llImageIndicator?.handleViewPagerScroll(
      8,
      count,
      R.drawable.selected_blue_item_indicator,
      0
    )
  }

这就是您要做的一切。

答案 5 :(得分:1)

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;
}

答案 6 :(得分:0)

现在您可以使用ViewPager2

它基本上包装了RecyclerView(因此您将使用RecyclerView.Adapter),但是它允许在TabLayoutMediator的帮助下附加TabLayout

然后可以将TabLayout的样式设置为类似于点页面指示器。例如参见How do you create an Android View Pager with a dots indicator?

答案 7 :(得分:0)

实际上有一个非常好的图书馆。 只需将其添加到您的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);

答案 8 :(得分:0)

我对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
        }

答案 9 :(得分:0)

如果有人在寻找 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);
    }

}

答案 10 :(得分:-1)

首先,您必须为圈子创建另一个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);
    }
}