美文网首页自定义控件
Android自定义View之RecyclerView.Item

Android自定义View之RecyclerView.Item

作者: 半生黑豆 | 来源:发表于2018-01-06 23:27 被阅读261次

    现在自己所写的项目中用到列表的地方基本全部使用的是RecyclerView,而使用列表就会有非常大的几率用到分割线功能。以前在用到RecyclerView的分割线功能的时候,都是利用RecyclerView自身设置的背景色和每个Item的背景色的差异,然后继承RecyclerView.ItemDecoration,并且简单的重写
    getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
    方法就能达到效果了。简单实现一个分割线效果代码如下

      public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            switch (mOrientation) {
                case LinearLayoutManager.HORIZONTAL:
                    outRect.right = mSpace;
                    if (parent.getChildAdapterPosition (view) == 0)
                        outRect.left = mSpace;
                    break;
                case LinearLayoutManager.VERTICAL:
                    outRect.bottom = mSpace;
                    if (parent.getChildAdapterPosition (view) == 0)
                        outRect.top = mSpace;
                    break;
            }
        }
    

    这样就能实现最简单的一个分割线的效果了。可是今天在写项目的时候遇到了分割线颜色可能会变动的情况。因此上面这种简单的方式就不能满足要求了。

    首先,Google了一番发现,原来ItemDecoration能做的事远远不止这么简单,它还能定制出更多的酷炫的效果,不仅能在“Item下面”绘制分割线效果,还能在“Item上面”盖章。要定制更多的效果就要重写ItemDecoration的另外两个方法
    onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)

    onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)
    onDraw方法要比Item 的onDraw方法先执行,所以它画出的东西总是在Item的下面,在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 childView 的底下,所以并不可见,但是会存在overDraw。而onDrawOver 方法是在Item的onDraw执行之后才执行,所以onDrawOver画出的东西是在Item的上面。仔细学习了一下onDrawOver方法,简直像发现了新大陆一样。此次项目中,有个列表左边需要展示一张图片,中间是4行文字描述,然后在右边还是一张图片,不过这张图片要有盖在文字上的效果类似于下面这样

    没有原型,自己画了个
    我原本是用xml布局文件拼凑了这个效果。但是,仔细研究了onDrawOver方法后,发现用onDrawOver也可以很好的实现此效果。xml文件代码行数固然减少了很多而且加载效率肯定也有所提升,不过最重要的是感觉B格瞬间上升了很大一截

    下面先放上自定义ItemDecoration的完整代码
    public class MyItemDecoration extends RecyclerView.ItemDecoration {
        private Drawable mDivider;
        private int mSize;
        private int mOrientation;
        private Paint mPaint;
        private Bitmap bitmap;
    
        public MyItemDecoration (Context context, int color, int drawableId, int size, int orientation) {
            mDivider = new ColorDrawable (color);
            mSize = size;
            mOrientation = orientation;
            bitmap = BitmapFactory.decodeResource (context.getResources (), drawableId);
    
            mPaint = new Paint ();
            mPaint.setColor (Color.RED);
            mPaint.setStyle (Paint.Style.FILL);
        }
    
        @Override
        public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
          switch (mOrientation){
                case  LinearLayoutManager.HORIZONTAL:
                    outRect.right += mSize;
                    break;
                case LinearLayoutManager.VERTICAL:
                    outRect.bottom += mSize;
                    break;
            }
        }
    
    
        @Override
        public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
            int left, right, top, bottom;
            if (mOrientation == LinearLayoutManager.VERTICAL) {
                left = parent.getPaddingLeft ();
                right = parent.getWidth () + parent.getPaddingRight ();
                int count = parent.getChildCount ();
                for (int i = 0; i < count; i++) {
                    View child = parent.getChildAt (i);
                    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams ();
                    top = child.getBottom () + params.bottomMargin;
                    bottom = top + mSize;
                    mDivider.setBounds (left, top, right, bottom);
                    mDivider.draw (c);
                }
            }
        }
    
        @Override
        public void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state) {
            int left, top;
            if (mOrientation == LinearLayoutManager.VERTICAL) {
                int childCount = parent.getChildCount ();
                for (int i = 0; i < childCount; i++) {
                    View child = parent.getChildAt (i);
                    left = child.getWidth () - bitmap.getWidth () - 15;
                    top = child.getBottom () / 2 + (child.getBottom () / 2 * i) / (i + 1) - bitmap.getHeight () / 2;
                    c.drawBitmap (bitmap, left, top, mPaint);
                }
            }
        }
    }
    

    下面是自己测试的实现效果



    感觉很完美,右边“异常”标签的位置也可以通过计算调整。

    在自己摸索着写这个MyItemDecoration 的过程中,遇到了个问题一并记录一下。以前都是简单的用,并没有深入的了解过ItemDecoration,也算给自己提个醒,以后不管学习还是工作,尽量多多的去延伸知识面
    问题:只重写onDraw方法也可以实现分割线的效果,那么getItemOffsets 的作用是又是什么呢?
    在遇到这个问题的时候,我觉得我占了一点好运气,因为我给每个Item都设置了一个边框,如果没有这个边框,可能我会以为只重写onDraw的效果就是正确的,因为它“看起来”的效果确实是正确的。只重写onDraw方法的时候出现的效果是这样的

    不重写getItemOffsets方法.png
    有分割线的效果,但是边框却“越过”了分割线和下一个Item相连了,于是,我又重写getItemOffsets方法,此时的效果就正常了,如下
    设置分割线.png
    可是,为什么会出现“越过”分割线的效果呢?经过我自己多次对照代码运行测试后发现,画第一个分割线的开始位置是这样计算的
    public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
               ......
         for (int i = 0; i < childCount; i++) {
              View child = parent.getChildAt (i);
              top = child.getBottom () + params.bottomMargin;
              bottom = top + mSize;
               ......
            }
    

    取得Item1.getBottom()作为第一条分割线的top,然后让top加上设置的分割线的高度mSize作为第一条分割线的bottom,余下的分割线以此类推,此时不重写getItemOffsets的情况下,画个草图更加一目了然


    我是草图.png

    虽然通过onDraw方法画出了分割线的效果,但是由于没有重写getItemOffsets()方法,所以RecyclerView的每个Item还是按照原始的布局位置设置,从3个红色箭头也可以看出
    图片顶部到Item1 top的距离 > 图片到Item(x) top的距离,(x=2,3,4...)
    这样从Item2开始每个Item的顶部都少了一截mSize大小的高度,这就也解释了为什么会出现边框“越过”分割线的原因

    相关文章

      网友评论

        本文标题:Android自定义View之RecyclerView.Item

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