How to make a page indicator for horizontal recyclerview

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

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

相关标签:
10条回答
  • 2021-01-30 05:46

    Nowadays you could use ViewPager2.

    It basically wraps a RecyclerView (so you will be using a RecyclerView.Adapter) but it allows the attachment of a TabLayout with the help of a TabLayoutMediator.

    The TabLayout can then be styled to act like a dot page indicator. See for example How do you create an Android View Pager with a dots indicator?

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

    You can add PageIndicator.java class to your .xml below recycler view widget

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_white">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_notice_board_cards"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dimen_16dp"
            android:orientation="horizontal"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_high_lights" />
    
        <com.abc.widget.PageIndicator
            android:id="@+id/ll_image_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|start"
            android:layout_marginStart="@dimen/dimen_16dp"
            android:layout_marginTop="@dimen/dimen_12dp"
            android:layout_marginEnd="@dimen/dimen_16dp"
            android:gravity="center"
            android:orientation="horizontal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/tv_cards_count"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/rv_notice_board_cards"
            app:layout_constraintVertical_bias="0.0" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    The PageIndicator.java class like below

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

    In your recycler view adapter, you can add override below with interface

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

    and last in your Activity or Fragment you can add below code initially to call Page Indicator method.

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

    In above code you can add your drawable for selected_blue_item_indicator like below

    <?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>
    

    and once you override the updateCount() method in Activity or Fragment call handleViewPagerScroll() method of Page Indicator

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

    that's all you have to do.

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

    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 !!!!!

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

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

    I made adjustments to CirclePagerIndicatorDecoration, so it will support RTL (Right-to-Left) languages. It took me a couple of a days, hope it will help someone:

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

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