RecyclerView addItemDecoration 的

作者: 程序员徐公 | 来源:发表于2019-04-23 19:02 被阅读12次

    前言

    RecyclerView,在开发当中使用非常频繁的一个控件,今天,主要讲解以下两个问题

    • 添加分割线
    • item 间距的平均分布

    addItemDecoration 方法简介

    我们先来看一下 addItemDecoration 方法

    官网链接

    Add an RecyclerView.ItemDecoration to this RecyclerView. Item decorations can affect both measurement and drawing of individual item views.

    Item decorations are ordered. Decorations placed earlier in the list will be run/queried/drawn first for their effects on item views. Padding added to views will be nested; a padding added by an earlier decoration will mean further item decorations in the list will be asked to draw/pad within the previous decoration's given area.

    简单来来说,ItemDecoration 是 itemView 的装饰,可以影响 itemView 的 measurement 和 draw。

    ItemDecoration 主要有几个方法

    getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

    Retrieve any offsets for the given item. Each field of outRect specifies the number of pixels that the item view should be inset by, similar to padding or margin. The default implementation sets the bounds of outRect to 0 and returns.

    可以影响 item 的大小,类似于在 item 中设置 padding 和 margin。

    void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state)

    Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn before the item views are drawn, and will thus appear underneath the views.

    在 itemView 之前绘制

    void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)

    Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn after the item views are drawn and will thus appear over the views.

    在 itemView 之后绘制


    添加分割线

    效果图如下

    image

    RecyclerViewDivider,已支持以下功能

    1. 自定义分割线,设置 drawable
    2. 设置分割线高度,颜色
    3. 设置分割线距离屏幕左边,右边的距离
    4. 设置是否显示最后一条分割线

    详情代码见 RecyclerViewSample

    实现思路

    我们知道 RecyclerView 没有像之前 ListView 提供 divider 属性,设置分割线的话有挺多人在 itemView 的布局里面加个 1dp 左右的 view,根据业务场景设置是否可见。这是其中的一种方法,但其实,我们也可以使用 recyclerView.addItemDecoration() 来实现,主要需要重写 getItemOffsets 和 onDraw 方法

    思路很简单

    1. 重写 getItemOffsets,加上 divider 的高度,影响 itemView 的最终 size
    2. 在 onDraw 方法,根据 LinearLayoutManager 的方向分别绘制分割线
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(0, 0, 0, mDividerHeight);
    }
    
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            drawVerticalDivider(c, parent);
        } else {
            drawHorizontalDivider(c, parent);
        }
    }
    
    //Draw item dividing line
    private void drawVerticalDivider(Canvas canvas, RecyclerView parent) {
        // mLeftOffset 为自己设置的左边偏移量
        final int left = parent.getPaddingLeft() + mLeftOffset;
        // mRightOffset 为设置的右边偏移量
        final int right = parent.getMeasuredWidth() - parent.getPaddingRight() + mRightOffset;
        final int childSize = parent.getChildCount();
    
        if (childSize <= 0) {
            return;
        }
    
        // 从第一个 item 开始绘制
        int first = mStart;
        // 到第几个 item 绘制结束
        int last = childSize - mEnd - (mIsShowLastDivider ? 0 : 1);
        Log.d(TAG, " last = " + last + " childSize =" + childSize + "left = " + left);
    
        if (last <= 0) {
            return;
        }
    
        for (int i = first; i < last; i++) {
            drawableVerticalDivider(canvas, parent, left, right, i, mDividerHeight);
        }
    
    }
    
    private void drawableVerticalDivider(Canvas canvas, RecyclerView parent, int left, int right, int i, int dividerHeight) {
        final View child = parent.getChildAt(i);
    
        if (child == null) {
            return;
        }
    
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
        final int top = child.getBottom() + layoutParams.bottomMargin;
        final int bottom = top + dividerHeight;
    
        // 适配 drawable
        if (mDivider != null) {
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
    
        // 适配分割线
        if (mPaint != null) {
            canvas.drawRect(left, top, right, bottom, mPaint);
        }
    }
    
    
    

    Item 间距平均分布

    针对 GridLayoutManager 的

    在 Android 开发当中,我们经常会看到这样的界面,

    image

    一般来说,可能有以下几种需求:

    1. 要求第一列和最后一列距离屏幕的距离 A 是固定的,其余每个 item 之间的距离 B 也是固定的(但 A 不等于 B
    2. 要求第一列和最后一列距离屏幕的距离 A 是固定的,item 的大小是固定的,其余每个 item 之间的距离跟随分辨率的大小变化
    3. 第一行距离顶部的距离可以设置,最后一行距离底部的距离可以设置

    思路分析

    首先,我们知道,对于 GridLayoutmanager ,当我们设置的 spancount 为 3 的时候,那么每个 item
    的最大宽度为 itemMaxW = recycylerW / spancount = recycylerW / 3.

    假设我们 spancount 为 3,那么在不设置 itemDercation 的情况下它的分布是这样的,可以看到第一列与最后一行的距离是不一样的

    image

    而加上 itemDercation 之后,我们所看到的 item 是这样的,因此,我们可以分别对每个 item 的 ouctRect 进行处理

    image

    核心代码如下:

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    
        int top = 0;
        int left;
        int right;
        int bottom;
    
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        mSpanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
    
        // 屏幕宽度-View的宽度*spanCount 得到屏幕剩余空间
        int maxDividerWidth = getMaxDividerWidth(view);
        int spaceWidth = mFirstAndLastColumnW;//首尾两列与父布局之间的间隔
        // 除去首尾两列,item与item之间的距离
        int eachItemWidth = maxDividerWidth / mSpanCount;
        int dividerItemWidth = (maxDividerWidth - 2 * spaceWidth) / (mSpanCount - 1);//item与item之间的距离
    
        left = itemPosition % mSpanCount * (dividerItemWidth - eachItemWidth) + spaceWidth;
        right = eachItemWidth - left;
        bottom = dividerItemWidth;
    
        // 首行
        if (mFirstRowTopMargin > 0 && isFirstRow(parent, itemPosition, mSpanCount, childCount)) {
            top = mFirstRowTopMargin;
        }
    
        //最后一行
        if (isLastRow(parent, itemPosition, mSpanCount, childCount)) {
            if (mLastRowBottomMargin < 0) {
                bottom = 0;
            } else {
                bottom = mLastRowBottomMargin;
            }
        }
    
        Log.i(TAG, "getItemOffsets: dividerItemWidth =" + dividerItemWidth + "eachItemWidth = " + eachItemWidth);
    
        Log.i(TAG, "getItemOffsets: itemPosition =" + itemPosition + " left = " + left + " top = " + top
                + " right = " + right + " bottom = " + bottom);
    
        outRect.set(left, top, right, bottom);
    }
    
    
    

    在代码中使用

    mRecyclerView.setLayoutManager(layoutManager);
    int firstAndLastColumnW = DisplayUtils.dp2px(this, 15);
    int firstRowTopMargin = DisplayUtils.dp2px(this, 15);
    GridDividerItemDecoration gridDividerItemDecoration =
            new GridDividerItemDecoration(this, firstAndLastColumnW, firstRowTopMargin, firstRowTopMargin);
    gridDividerItemDecoration.setFirstRowTopMargin(firstRowTopMargin);
    gridDividerItemDecoration.setLastRowBottomMargin(firstRowTopMargin);
    mRecyclerView.addItemDecoration(gridDividerItemDecoration);
    List<Integer> imageList = DataUtils.produceImageList(30);
    ImageAdapter imageAdapter = new ImageAdapter(this, imageList);
    
    mRecyclerView.setAdapter(imageAdapter);
    
    

    针对横向的 LinearlayoutManager

    image
    @Override
    public void getItemOffsets(Rect outRect, View view,
                               RecyclerView parent, RecyclerView.State state) {
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        int childCount = parent.getAdapter().getItemCount();
    
        if (itemPosition == 0) {
            outRect.left = firstAndlastRect.left;
            outRect.right = firstAndlastRect.right;
            outRect.bottom = firstAndlastRect.bottom;
            outRect.top = firstAndlastRect.top;
            return;
        }
    
        if (itemPosition == childCount - 1) {
            outRect.left = 0;
            outRect.right = firstAndlastRect.left;
            outRect.bottom = firstAndlastRect.bottom;
            outRect.top = firstAndlastRect.top;
            return;
        }
    
        setOutRect(outRect, space);
    }
    
    
    int j = DisplayUtils.dp2px(this, 15);
    Rect firstAndLastRect = new Rect(j, i, i, 0);
    HorizontalSpacesDecoration spacesDecoration = new HorizontalSpacesDecoration(rect, firstAndLastRect);
    mRecyclerView.addItemDecoration(spacesDecoration);
    List<Integer> integers = DataUtils.produceImageList(R.mipmap.ty1, 30);
    ImageAdapter imageAdapter = new ImageAdapter(this, integers);
    mRecyclerView.setAdapter(imageAdapter);
    
    

    RecyclerViewSamplehttps://github.com/gdutxiaoxu/RecyclerViewSample

    Android 技术人

    扫一扫,欢迎关注我的公众号。如果你有好的文章,也欢迎你的投稿。

    相关文章

      网友评论

        本文标题:RecyclerView addItemDecoration 的

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