How to make a page indicator for horizontal recyclerview

前端 未结 10 1135
说谎
说谎 2021-01-30 05:32

Any idea how to create a page indicator for recyclerview list ?

相关标签:
10条回答
  • 2021-01-30 06:00

    Shoban's answer did not work for me, so here is how I got it to work.

    Keep in mind that I removed the animations as they were not necessary for my usecase. The result looks exactly like the screenshot attached in this question.

    Also, in order to allow scrolling only one element at a time, I used PagerSnapHelper() to achieve the desired result.

    PagerSnapHelper helper = new PagerSnapHelper();
    
    helper.attachToRecyclerView(recyclerView);
    
    recyclerView.addItemDecoration(new CirclePagerIndicatorDecoration());
    

    Here is my code for the decorator:

    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;
    }
    
    0 讨论(0)
  • 2021-01-30 06:01

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-30 06:03

    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

    0 讨论(0)
  • 2021-01-30 06:05

    there is actually a very good library for this purpose. just add this to your gradle dependencies

    implementation "ru.tinkoff.scrollingpagerindicator:scrollingpagerindicator:1.0.6"
    

    then add the layout

    <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" />
    

    then add the indicator to your recyclerview like this

    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);
    
    0 讨论(0)
提交回复
热议问题