How to make a page indicator for horizontal recyclerview

早过忘川 提交于 2019-12-02 15:37:17

You can add an indicator by using RecyclerView.ItemDecoration.

Just draw some lines or circles at the bottom and use layoutManager.findFirstVisibleItemPosition() to get the current active item. Since pagers tend to fill the whole width this is an accruate way of getting the displayed item. This also allows us to calculate the scrolling distance by comparing the childs left edge to the parents.

Below you find a sample decoration that draws some lines and animates between them

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

Which will give you a result like the following


There is also a blog post that goes more into detail about how the decoration works here and the full source code is available at GitHub

lbgupta

I have changed the code for circles. Removed the the code to draw line and the same is replaced with draw circle methods. Please find below the complete 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;
    }
}

Many Thanks, LB Gupta Happy Coding !!!!!

I have copied the same answer which is given by David Medenjak , but to make the circles below the recyclerview . I have updated few lines of code in the above answer , please have a look and use accordingly .

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

And Apply it to the recyclerview as follows

//for horizontal scroll for recycler view 
 LinearLayoutManager linearLayoutManager
                = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
recyclerview.setLayoutManager(linearLayoutManager);
recyclerview.addItemDecoration(new CirclePagerIndicatorDecoration());

in case of anyone need, I wrote my own library for this (after a lot of searching): RecyclerView indicator. Here's how you do it:

 <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);
Hanaa Mohamed

First you have to create another RecyclerView for the circles and put this code in the OnScrollListener of the first RecyclerView.

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);
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!