Recyclerview是我们日常开发中使用频率比较高的的控件,而其中的ItemDecoration作为布局装饰又能很方便的帮助我们定义分割线,列表排行效果以及设置布局悬浮置顶效果等,所以有必要去了解ItemDecoration的诸多细节。
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); } }
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() ); }
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出来蛮久的了,可以大大提升开发效率。
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();
看下面只需要知己设置drawable就可以了,你想要什么形状就可以添加什么样的形状。
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); dividerItemDecoration.setDrawable(ContextCompat.getDrawable(this,R.drawable.shape_devider_line_red)); mRecyclerView.addItemDecoration(dividerItemDecoration);
如上右图所示
前面已经分析了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);