ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Android 活用RecyclerView分割线

2022-10-26 13:09:59  阅读:483  来源: 互联网

标签:android isAndroid RecyclerView 分割线


1、ItemDecoration简介

Recyclerview是我们日常开发中使用频率比较高的的控件,而其中的ItemDecoration作为布局装饰又能很方便的帮助我们定义分割线,列表排行效果以及设置布局悬浮置顶效果等,所以有必要去了解ItemDecoration的诸多细节。

2、基本原理

public class MyDecoration extends RecyclerView.ItemDecoration{
    /**
     * step1 相当于itemView外还有一个矩形,我们可以自由在itemview上下左右设置空余部分,
通过outRect 的left,top,right,bottom 来进行设置,我们可在这块控件进行绘制
     * 目标针对每一个item个体
     */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    }

    /**
     * step2 在item间绘制  常用于绘制分割线
     * 针对整个Recyclerview 绘制需要循环遍历item子布局 然后方能针对具体的item进行增加绘制
     */
    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

    /**
     * 在item上绘制
     */
    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
    }
}

2.1 getItemoffsets在RecyclerView源码中作用时机

manager#getItemDecorInsetsForChild

为指定view计算分割线矩形

Rect getItemDecorInsetsForChild(View child) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        } else if (this.mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            return lp.mDecorInsets;
        } else {
            Rect insets = lp.mDecorInsets;
            insets.set(0, 0, 0, 0);
            int decorCount = this.mItemDecorations.size();

            for(int i = 0; i < decorCount; ++i) {
                this.mTempRect.set(0, 0, 0, 0);
                ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).getItemOffsets(this.mTempRect, child, this, this.mState);
                insets.left += this.mTempRect.left;
                insets.top += this.mTempRect.top;
                insets.right += this.mTempRect.right;
                insets.bottom += this.mTempRect.bottom;
            }

            lp.mInsetsDirty = false;
            return insets;
        }
    }

        // 测量的时候 当作padding值放入其中
        public void measureChild(@NonNull View child, int widthUsed, int heightUsed) {
            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
            Rect insets = this.mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;
            int widthSpec = getChildMeasureSpec(this.getWidth(), this.getWidthMode(), this.getPaddingLeft() + this.getPaddingRight() + widthUsed, lp.width, this.canScrollHorizontally());
            int heightSpec = getChildMeasureSpec(this.getHeight(), this.getHeightMode(), this.getPaddingTop() + this.getPaddingBottom() + heightUsed, lp.height, this.canScrollVertically());
            if (this.shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }

        }

manager#calculateItemDecorationsForChild

manager向外部提供的获取分割线矩形数据的api

public void calculateItemDecorationsForChild(@NonNull View child, @NonNull Rect outRect) {
            if (this.mRecyclerView == null) {
                outRect.set(0, 0, 0, 0);
            } else {
                Rect insets = this.mRecyclerView.getItemDecorInsetsForChild(child);
                outRect.set(insets);
            }
        }

可解决问题:如何获取指定itemView的分割线的高度

Rect rect = new Rect();
View view =  mRvConflict.getLayoutManager().findViewByPosition(1);
 if (view != null){
                    mRvConflict.getLayoutManager().calculateItemDecorationsForChild(view,rect);
     Log.e("decoration", "高度 = "+rect.height() );
 }

3. 分割线使用

3.1无任何分割线的RecyclerView

private void initAdapter() {
     mMyRecvAdapter = new MyRecvAdapter(mContext,mList);
     LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
     linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
     mRecyclerView.setLayoutManager(linearLayoutManager);
     mRecyclerView.setAdapter(mMyRecvAdapter);
  }

相信大家对如何说些一个RecyclerView列表再熟悉不过了吧,不过推荐大家可以使用BRVAH出来蛮久的了,可以大大提升开发效率。

3.2 一行代码添加系统分割线

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

运行效果:

分析一下: RecyclerView 为我们提供的DividerItemDecoration的用于添加分割线的类中 代码其实不多,主要是继承了ItemDecoration并实现了 getItemOffsets()和onDra()方法,这里的getItemOffsets( )实际上是在条目之间分配一块矩形区域用来放置我们的分割线,注意一下方法参数Rect outRect,而通过onDraw( )方法把条目画在刚刚申请的间隔矩形中。没错就是这么简单,有兴趣的可以对照着源码细看一下。

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    ......

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
        // 设置矩形区域 
        // set(left,top,right,bottom) = outRect.left = left ...
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

   .......
}

getitemOffset可依据混排的条目数 设置间距效果

View view = null;
        GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
        int spanSize = layoutParams.getSpanSize();
        // 获得每行的下标 0,1
        int spanIndex = layoutParams.getSpanIndex();

3.3 不用系统的分割线,自己设定分割线的宽度和颜色

看下面只需要知己设置drawable就可以了,你想要什么形状就可以添加什么样的形状。

DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
 dividerItemDecoration.setDrawable(ContextCompat.getDrawable(this,R.drawable.shape_devider_line_red));
 mRecyclerView.addItemDecoration(dividerItemDecoration);

如上右图所示

3. 4 扩展

前面已经分析了DeivderItemDecoration的实现了,我们也可以自己手动撸一个来实现不是嘛,我们可以发现前面虽然是可以设置drawable进去,那么每次还需要写一个drawable文件或者弄张.9图进去,但通常的业务环境是只需要带颜色的线条就可以了吧,所以基于此我们可以自己手写一个。

直接上代码了额:

/**
 * @author zxl on 2018/7/14.
 *         discription: 自定义分割线支持水平和垂直布局 设置默认,图片和颜色为分割线
 */

public class MyRecyclerViewDivider extends RecyclerView.ItemDecoration {
    private Paint mPaint;
    private Drawable mDivider;
    private int mDividerHeight;
    private int mOrientation;
    /**
     *  系统默认的分割线
     * */
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};

    /**
     *  设置默认的分割线
     *  @param orientation 指代分割线的方向
     * */
    public MyRecyclerViewDivider(Context context, int orientation) {
        this.mDividerHeight = 2;
        if(orientation != 1 && orientation != 0) {
            throw new IllegalArgumentException("请输入正确的参数!");
        } else {
            this.mOrientation = orientation;
            TypedArray a = context.obtainStyledAttributes(ATTRS);
            this.mDivider = a.getDrawable(0);
            a.recycle();
        }
    }

    /**
     *  设置图片资源为分割线
     * */
    public MyRecyclerViewDivider(Context context, int orientation, int drawableId) {
        this(context, orientation);
        this.mDivider = ContextCompat.getDrawable(context, drawableId);
        this.mDividerHeight = this.mDivider.getIntrinsicHeight();
    }

    /**
     *  设置线条颜色和高度给分割线
     * */
    public MyRecyclerViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
        this(context, orientation);
        this.mDividerHeight = dividerHeight;
        this.mPaint = new Paint(1);
        this.mPaint.setColor(dividerColor);
        this.mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(0, 0, 0, this.mDividerHeight);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if(this.mOrientation == 1) {
            this.drawVertical(c, parent);
        } else {
            this.drawHorizontal(c, parent);
        }
    }

    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final  int left;
        final int right;
        // 判断RecyclerView是否要裁剪padding值
        if (parent.getClipToPadding()){
            // 需要裁剪那么就进行裁剪
            left = parent.getPaddingLeft();
            right = parent.getWidth()-parent.getPaddingRight();
            // 裁剪rv可视区域,  限制视图在该区域内是可见的,很重要的,这里
            canvas.clipRect(left,parent.getPaddingTop(),right,parent.getHeight()-parent.getPaddingBottom());
        }else {
            //  不裁剪则宽贼为rv的宽
            left = 0;
            right = parent.getWidth();
        }

        int childSize = parent.getChildCount();
        for(int i = 0; i < childSize; ++i) {
            View child = parent.getChildAt(i);
            // 获得item的信息包
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)child.getLayoutParams();
            // 分割线顶部 = item的底部 + item到底部的距离 + 动画偏移
            int top = child.getBottom() + layoutParams.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
            // 分割线底部 =  分割线底部 + 高度
            int bottom = top + this.mDividerHeight;
            if(this.mDivider != null) {
                this.mDivider.setBounds(left, top, right, bottom);
                this.mDivider.draw(canvas);
            }

            if(this.mPaint != null) {
                canvas.drawRect((float)left, (float)top, (float)right, (float)bottom, this.mPaint);
            }
        }
        canvas.restore();
    }

    private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int top;
        final int bottom;
        if (parent.getClipToPadding()){
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(),top,parent.getWidth()-parent.getPaddingRight(),bottom);
        }else {
            top  = 0;
            bottom = parent.getHeight();
        }
        int childSize = parent.getChildCount();
        for(int i = 0; i < childSize; ++i) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)child.getLayoutParams();
            int left = child.getRight() + layoutParams.rightMargin + Math.round(ViewCompat.getTranslationX(child));
            int right = left + this.mDividerHeight;
            if(this.mDivider != null) {
                this.mDivider.setBounds(left, top, right, bottom);
                this.mDivider.draw(canvas);
            }

            if(this.mPaint != null) {
                canvas.drawRect((float)left, (float)top, (float)right, (float)bottom, this.mPaint);
            }
        }
        canvas.restore();
    }
}

用法如下也是很简单,优点不用多说既囊括了系统提供的也支持drawable设置而且支持简易的线条颜色等,用途很大对的吧。

mRecyclerView.addItemDecoration(new MyRecyclerViewDivider(this, LinearLayoutManager.HORIZONTAL,2,ContextCompat.getColor(this,R.color.colorAccent)));

效果我下方左图所示

弄个表格出来

以下为 addItemDecoration的源码,可以发现的是可以设置多条分割线,哈哈,那么我们不就能画表格了嘛,6666

public void addItemDecoration(ItemDecoration decor, int index) {
        if (mLayout != null) {
            mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                    + " layout");
        }
        if (mItemDecorations.isEmpty()) {
            setWillNotDraw(false);
        }
        if (index < 0) {
            mItemDecorations.add(decor);
        } else {
            mItemDecorations.add(index, decor);
        }
        markItemDecorInsetsDirty();
        requestLayout();
    }

activity中的代码如下:

mMyRecvAdapter = new MyRecvAdapter(mContext,mList);
        mRecyclerView.addItemDecoration(new MyRecyclerViewDivider(this, LinearLayoutManager.HORIZONTAL,2,ContextCompat.getColor(this,R.color.colorAccent)));
        mRecyclerView.addItemDecoration(new MyRecyclerViewDivider(this,LinearLayoutManager.VERTICAL,2,ContextCompat.getColor(this,R.color.colorAccent)));
        mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
        mRecyclerView.setAdapter(mMyRecvAdapter);

标签:android,isAndroid,RecyclerView,分割线
来源:

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有