美文网首页Android开发程序员Android技术知识
RecyclerView添加自定义ItemDecoration实

RecyclerView添加自定义ItemDecoration实

作者: 请叫我张懂 | 来源:发表于2017-09-14 14:02 被阅读395次
    • 对于RecyclerView的使用想必大家都很熟悉,下面先看两张LinearLayoutManager和GridLayoutManager的示例
    LinearLayoutManager_GridLayoutManager.png
    • 上面展示的是没有分割线的示例,一般情况下我们是通过继承RecyclerView.ItemDecoration类并重写 getItemOffsets 与 onDraw 来实现自定义分割线。

    代码:

     public class CommonDecoration extends RecyclerView.ItemDecoration {
         public CommonDecoration() {
    
         }
        
        /**
         * @param outRect 用于规定分割线的范围
         * @param view    进行分割线操作的子view
         * @param parent  父view
         * @param state   (这里暂时不使用)
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,    RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
        }
    
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
        }
        
    }
    

    SquareTextView.java

    public class SquareTextView extends AppCompatTextView {
        public SquareTextView(Context context) {
            super(context);
        }
    
        public SquareTextView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public SquareTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int size = MeasureSpec.getSize(widthMeasureSpec);
            setMeasuredDimension(size, size);
        }
    }
    

    item_grid.xml

    <?xml version="1.0" encoding="utf-8"?>
    <com.zzz.recyclerviewdemo.view.SquareTextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/tv_letter"
        android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:text="A"
        android:textColor="@android:color/white"
        android:textSize="20dp" />
    

    item_linear.xml

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/tv_letter"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:text="A"
        android:textColor="@android:color/white"
        android:textSize="20dp" />
    

    分割线使用:

    mRecyclerView.addItemDecoration(new CommonDecoration(this, R.drawable.drawable_item_decoration));
    

    LinearLayoutManager实现分割线

    drawable_itemdecoration.xml:

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!--用于设置分割线的宽-->
        <size android:width="5dp" />
        <solid android:color="@android:color/holo_orange_dark" />
    </shape>
    

    代码:

    public class CommonDecoration extends RecyclerView.ItemDecoration {
    
        private Context mContext;
        private Drawable mDrawable;
        
        public CommonDecoration(Context context, int drawableId) {
            this.mContext = context;
            this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
        }
        
        /**
         * @param outRect 用于规定分割线的范围
         * @param view    进行分割线操作的子view
         * @param parent  父view
         * @param state   (这里暂时不使用)
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            outRect.bottom = mDrawable.getIntrinsicHeight();
        }
        
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                drawHorizontalDecoration(c, parent.getChildAt(i));
            }
        }
        
        private void drawHorizontalDecoration(Canvas canvas, View childView) {
            Rect rect = new Rect(0, 0, 0, 0);
            rect.top = childView.getBottom();
            rect.bottom = rect.top + mDrawable.getIntrinsicHeight();
            rect.left = childView.getLeft();
            rect.right = childView.getRight();
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    }
    
    • 简单的分割线的截图
    linearLayoutManager_without_isLastRow.png

    问题:很明显可以看出在最后一个Item("Z")也出现了分割线,即对于显示LinearLayoutManager的最后一个子view的时候不应该存在分割线。

    解决:在getItemOffsets方法中进行判断是否为最后一行,使用(currentItemPosition + 1) 与 totalItems比较(currentItemPosition从0开始计数)。如果是最后一行就不给其留出空间,即让outRect.bottom = 0。


    代码:

    public class CommonDecoration extends RecyclerView.ItemDecoration {
    
        private Context mContext;
        private Drawable mDrawable;
        //
        private int mTotalItems;//总Item数
    
        public CommonDecoration(Context context, int drawableId) {
            this.mContext = context;
            this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
        }
    
        /**
         * @param outRect 用于规定分割线的范围
         * @param view    进行分割线操作的子view
         * @param parent  父view
         * @param state   (这里暂时不使用)
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (0 == mTotalItems)
                mTotalItems = parent.getAdapter().getItemCount();
            //在源码中有一个过时的方法,里面有获取当前ItemPosition
            int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
            if (!isLastRow(currentItemPosition, mTotalItems))
                outRect.bottom = mDrawable.getIntrinsicWidth();
            else
                outRect.bottom = 0;
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                drawHorizontalDecoration(c, parent.getChildAt(i));
            }
        }
    
        private void drawHorizontalDecoration(Canvas canvas, View childView) {
            Rect rect = new Rect(0, 0, 0, 0);
            rect.top = childView.getBottom();
            rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
            rect.left = childView.getLeft();
            rect.right = childView.getRight();
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    
        private boolean isLastRow(int currentItemPosition, int totalItems) {
            boolean result = false;
            if (currentItemPosition + 1 >= totalItems)
                result = true;
            return result;
        }
    }
    
    • 解决问题后的截图
    linearLayoutManager_with_isLastRow.png

    问题:对比两张图可以看出如果子view的数量充满整个屏幕的话是可以不显示分割线的,但是如果子view的数量无法充满整个屏幕的话,分割线又出现了。

    解决:上一步我们只更改了getItemOffsets方法中如果是最后一行不给出画分割线的空间,但是如果子view的数量不够的话还是存在画分割线的空间。所以我们还要修改drawHorizontalDecoration方法 ,在该方法中同样判断是否为最后一行。


    代码:

     private void drawHorizontalDecoration(Canvas canvas, View childView) {
        int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
        if (isLastRow(currentItemPosition, mTotalItems)) {
            return;
        }
        //
        Rect rect = new Rect(0, 0, 0, 0);
        rect.top = childView.getBottom();
        rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
        rect.left = childView.getLeft();
        rect.right = childView.getRight();
        mDrawable.setBounds(rect);
        mDrawable.draw(canvas);
    }
    
    • 解决问题后的截图
    linearLayoutManager_with_isLastRow_plus.png

    GridLayoutManager实现分割线

    • 使用LinearLayoutManager的Decoration截图
    GridLayoutManager_use_LinearLayoutManager_decoration.png

    问题:可以看出使用了LinearLayoutManager的话只有view的下部分存在分割线,而且它最后一行存在一些问题,我们只去掉了最后一个子view的分割线,而不是一行的分割线。

    解决:

    1. 最后一行分割线的问题,所以需要更改 isLastRow 方法。从下示例图中可以看出最后一行跟RecyclerView的列数有关。所以只要判断(currentItemPosition + 1) > ((行数 - 1) x 列数)即可。这样也不会对LinearLayoutManger产生影响。
    2. 因为除了View在下部要有分割线,在子view右部也要有,所以必须设置outRect.right,为右部留出画分割线的空间。
    statement.png

    代码:

    public class CommonDecoration extends RecyclerView.ItemDecoration {
    
        private Context mContext;
        private Drawable mDrawable;
        //
        private int mTotalItems;//总Item数
        private int mSpanCount;//总列数
    
        public CommonDecoration(Context context, int drawableId) {
            this.mContext = context;
            this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
        }
    
        /**
         * @param outRect 用于规定分割线的范围
         * @param view    进行分割线操作的子view
         * @param parent  父view
         * @param state   (这里暂时不使用)
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (0 == mTotalItems)
                mTotalItems = parent.getAdapter().getItemCount();
            if (0 == mSpanCount) {
                RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
                //判断是否为GridLayoutManager
                if (layoutManager instanceof GridLayoutManager) {
                    GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                    mSpanCount = gridLayoutManager.getSpanCount();
                } else {
                    mSpanCount = 1;
                }
            }
            //在源码中有一个过时的方法,里面有获取当前ItemPosition
            int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
            //
            if (!isLastRow(currentItemPosition, mTotalItems, mSpanCount))
                outRect.bottom = mDrawable.getIntrinsicWidth();
            else
                outRect.bottom = 0;
            //
            outRect.right = mDrawable.getIntrinsicWidth();
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                drawHorizontalDecoration(c, parent.getChildAt(i));
                drawVerticalDecoration(c, parent.getChildAt(i));
            }
        }
    
        private void drawHorizontalDecoration(Canvas canvas, View childView) {
            int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
            if (isLastRow(currentItemPosition, mTotalItems, mSpanCount)) {
                   return;
            }
            //
            Rect rect = new Rect(0, 0, 0, 0);
    
            rect.top = childView.getBottom();
            rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
            rect.left = childView.getLeft();
            rect.right = childView.getRight();
    
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    
        private void drawVerticalDecoration(Canvas canvas, View childView) {
            Rect rect = new Rect(0, 0, 0, 0);
    
            rect.top = childView.getTop();
            rect.bottom = childView.getBottom();
            rect.left = childView.getRight();
            rect.right = rect.left + mDrawable.getIntrinsicWidth();
    
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    
        private boolean isLastRow(int currentItemPosition, int totalItems, int spanCount) {
            boolean result = false;
            int rowCount = 0;
    
            if (0 == totalItems % spanCount) {
                rowCount = totalItems / spanCount;
            } else {
                rowCount = totalItems / spanCount + 1;
            }
            if ((currentItemPosition + 1) > (rowCount - 1) * spanCount)
                result = true;
    
            return result;
        }
    }
    
    • 解决问题后截图
    GridLayoutManager_use_LinearLayoutManager_solve.png

    问题: 从图中可以看出在水平与垂直方向相接处的分割线没有颜色填充;并且最后一列的的子view不应该有垂直方向的分割线。

    解决:

    1. 在 drawHorizontalDecoration 方法中的 rect.right 添加上分割线的宽度。
    2. 添加一个方法判断是否为最后一列使用(currentItemPosition + 1) % 3是否等于0来判断,如果是最后一列的话,则不留出画垂直方向分割线的空间。

    代码:

    public class CommonDecoration extends RecyclerView.ItemDecoration {
    
        private Context mContext;
        private Drawable mDrawable;
        //
        private int mTotalItems;//总Item数
        private int mSpanCount;//总列数
    
        public CommonDecoration(Context context, int drawableId) {
            this.mContext = context;
            this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
        }
    
        /**
         * @param outRect 用于规定分割线的范围
         * @param view    进行分割线操作的子view
         * @param parent  父view
         * @param state   (这里暂时不使用)
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (0 == mTotalItems)
                mTotalItems = parent.getAdapter().getItemCount();
            if (0 == mSpanCount) {
                RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
                //判断是否为GridLayoutManager
                if (layoutManager instanceof GridLayoutManager) {
                    GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                    mSpanCount = gridLayoutManager.getSpanCount();
                } else {
                    mSpanCount = 1;
                }
            }
            //在源码中有一个过时的方法,里面有获取当前ItemPosition
            int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
            //
            if (!isLastRow(currentItemPosition, mTotalItems, mSpanCount))
                outRect.bottom = mDrawable.getIntrinsicWidth();
            else
                outRect.bottom = 0;
            //
            if (!isLastColumn(currentItemPosition, mSpanCount)) {
                outRect.right = mDrawable.getIntrinsicWidth();
            } else {
                outRect.right = 0;
            }
    
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                drawHorizontalDecoration(c, parent.getChildAt(i));
                drawVerticalDecoration(c, parent.getChildAt(i));
            }
        }
    
        private void drawHorizontalDecoration(Canvas canvas, View childView) {
            int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
            if (isLastRow(currentItemPosition, mTotalItems, mSpanCount)) {
                return;
            }
            //
            Rect rect = new Rect(0, 0, 0, 0);
    
            rect.top = childView.getBottom();
            rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
            rect.left = childView.getLeft();
            rect.right = childView.getRight() + mDrawable.getIntrinsicWidth();
    
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    
        private void drawVerticalDecoration(Canvas canvas, View childView) {
            Rect rect = new Rect(0, 0, 0, 0);
    
            rect.top = childView.getTop();
            rect.bottom = childView.getBottom();
            rect.left = childView.getRight();
            rect.right = rect.left + mDrawable.getIntrinsicWidth();
    
            mDrawable.setBounds(rect);
            mDrawable.draw(canvas);
        }
    
        private boolean isLastRow(int currentItemPosition, int totalItems, int spanCount) {
            boolean result = false;
            int rowCount = 0;
    
            if (0 == totalItems % spanCount) {
                rowCount = totalItems / spanCount;
            } else {
                rowCount = totalItems / spanCount + 1;
            }
            if ((currentItemPosition + 1) > (rowCount - 1) * spanCount)
                result = true;
    
            return result;
        }
    
        private boolean isLastColumn(int currentItemPosition, int spanCount) {
            boolean result = false;
            if (0 == (currentItemPosition + 1) % spanCount)
                result = true;
            return result;
        }
    }
    

    解决问题后截图:

    final.png

    下一次将会从源码的角度 ,进一步分析当前存在的BUG。
    RecyclerView添加自定义ItemDecoration实现(2)

    相关文章

      网友评论

        本文标题:RecyclerView添加自定义ItemDecoration实

        本文链接:https://www.haomeiwen.com/subject/yzmrsxtx.html