Recyclerview 学习系类之ItemDecoration(一)

- 更多分享请看:[http://www.cherylgood.cn](http://www.cherylgood.cn)


#### Google官方解释


- An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.


- All ItemDecorations are drawn in the order they were added, before the item views (in onDraw() and after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State).


##### 个人理解:


大致意思是:


- ItemDecoration允许应用程序从适配器的数据集中为制定的view添加制定的图形和布局偏移量。该特性一般被用于在两个item之间绘制分割线,高亮度以及视觉分组等等。


- 所有的ItemDecorations都按照它们被添加的顺序在item被绘制之前(在onDraw方法中)和在Items被绘制之后(在onDrawOver(Canvas,RecyclerView,RecyclerView.State))进行绘制。


 可以看到,ItemDecoration是相当强大和灵活的。


#### method学习:


>[getItemOffsets](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, int, android.support.v7.widget.RecyclerView))([Rect](https://developer.android.com/reference/android/graphics/Rect.html) outRect, int itemPosition, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent)
This method was deprecated in API level 22.0.0. Use [getItemOffsets(Rect, View, RecyclerView, State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))


- 该方法在API 22.0.0之后已被废弃,我们可以看代替的方法


> [getItemOffsets](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))([Rect](https://developer.android.com/reference/android/graphics/Rect.html) outRect, [View](https://developer.android.com/reference/android/view/View.html) view, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent, [RecyclerView.State](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State.html) state)
Retrieve any offsets for the given item.


- 我们可以通过该方法中的outRect来设置item的padding值。比如你要在item底部添加一条分割线,此时为了不影响item原来的布局参数,我们一般会返回一个地步padding为某个pd的outRect,在recyclerview绘制item的时候会讲该布局数据加入,我们原来的item就会多出一个底部padding,是不是解耦的很完美呢?


>[onDraw](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView))([Canvas](https://developer.android.com/reference/android/graphics/Canvas.html) c, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent)
*This method was deprecated in API level 22.0.0. Override [onDraw(Canvas, RecyclerView, RecyclerView.State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))
*


- 该方法也已经过期了,看下面的


>[onDraw](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))([Canvas](https://developer.android.com/reference/android/graphics/Canvas.html) c, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent, [RecyclerView.State](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State.html) state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.


- 该方法会在绘制item之前调用,也就是说他的层级是在item之下的,通过该方法,我们可以爱绘制item之前绘制我们需要的内容。


>[onDrawOver](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))([Canvas](https://developer.android.com/reference/android/graphics/Canvas.html) c, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent, [RecyclerView.State](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State.html) state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.


- 该方法已过期,看下面的


> [onDrawOver](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView))([Canvas](https://developer.android.com/reference/android/graphics/Canvas.html) c, [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) parent)
*This method was deprecated in API level 22.0.0. Override [onDrawOver(Canvas, RecyclerView, RecyclerView.State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))
*


- 该方法于onDrawOver类似,在绘制item之后会调用该方法。


此时,也许你会疑问,他真的是这样执行的么?为了一探究竟,我们来看下源码吧。


     @Override
    public void draw(Canvas c) {
        super.draw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }...}
- 从recyclerview的源码中我们可以看到,在draw方法中后会遍历recyclerview里面的itemDecoration然后调用itemdecoration的onDrawOver方法;而recyclerview调用了super.draw(c)之后会先,父类会先调用recyclerview的onDraw方法;


       @Override
      public void onDraw(Canvas c) {
        super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        } }
- 在recyclerview的onDraw里又会调用itemDecoration的onDraw方法,当recyclerview的onDraw方法执行完之后,recyclerview的draw方法中super.draw(c);后面的代码才会继续执行,而recyclerview是在绘制了自己之后才会去绘制item。


- 结论:itemDecoration的onDraw方法在item绘制之前调用,itemDecoration的onDrawOver方法在绘制item之后调用。


接下来我们在看下getItemOffsets这个方法。他真的把我们的outRect加到item的布局参数里面了么?预知真相,看源码。


       Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }


        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
    }


- 首先我们可以看到,getItemOffsets这个方法在recyclerview的getItemDecorInsetsForChild 中被调用,该方法会把所有的itemDecortion中的rect累加后返回;我们再看下getItemDecorInsetsForChild在哪被调用的。


        public void measureChild(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();


            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);


- 在measureChild方法中被调用,也就是recyclerview在测量childView的时候
        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();


            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);


- 使用margins测量childView时会用到
- 结论,在getItemOffsets方法中outRect会影响到recyclerview中childView的布局。


使用ItemDecoration实现分割线的都调用过addItemDecoration方法。发现,只要调用一次addItemDecoration将自定义的分割线ItemDecoration添加进去就可以实现分割线效果了,如果我们添加多次会如何呢?


    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();
    }


- 从RecyclerView.addItemDecoration方法源码可以看到,内部使用了一个ArrayList类型的mItemDecorations存储我们添加的所有ItemDecoration。markItemDecorInsetsDirty方法有什么用呢?我们看下源码


      void markItemDecorInsetsDirty() {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = mChildHelper.getUnfilteredChildAt(i);
            ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
        }
        mRecycler.markItemDecorInsetsDirty();
      }


- 里面有一个mInsetsDirty被重置为true,最终调用mRecycler.markItemDecorInsetsDirty();我们继续看mRecycler.markItemDecorInsetsDirty();方法源码:


 void markItemDecorInsetsDirty() {
            final int cachedCount = mCachedViews.size();
            for (int i = 0; i < cachedCount; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
                if (layoutParams != null) {
                    layoutParams.mInsetsDirty = true;
                }
            }
        }


- 里面也是将layoutParams的mInsetsDirty重置为true,这个mInsetsDirty有什么用呢 ?我们继续看源码:


      Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }


        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
      }


- 看到这段代码感觉应该是它了,可以看到,
  - 判断childView的layoutParams的mInsetsDirty是不是false 是false直接返回mDecorInsets。
 - 判断itemDecoration是否已改变或者已不可用,mState.isPreLayout是recyclerview用来处理动画的。
  - 如果前面的都不是,就会从新调用itemDecoration的getItemOffsets方法,重新计算layout偏离值之后返回。


- 出于性能的考虑,如果之前为ChildView生成过DecorInsets,那么会缓存在ChildView的LayoutParam中(mDecorInsets), 同时为了保证mDecorInsets的时效性,还同步维护了一个mInsetsDirty标记在LayoutParam中
- 在获取ChidlView的DecorInsets时,如果其mInsetsDirty为false,那么代表缓存没有过期,直接返回缓存的mDecorInsets。
- 如果mInsetsDirty为true,表示缓存已过期,需要根据ItemDecoration集合重新生成 
  - 添加或者删除ItemDecoration的时候,会将所有ChildView包括Recycler中的mInsetsDirty设置为true来使DecorInsets缓存失效
 




>总结:其实getItemDecorInsetsForChild方法我们之前在本章前面有分析到。他就是在测量childView的时候会调用,所以如果我们的itemDecortion中途需要更新,我们需要调用markItemDecorInsetsDirty方法,然后调用requestLayout请求重新绘制,这样在重新绘制childView的时候,就会重新计算ItemDecortion中返回的layout偏离值。达到我们想要的效果。




- 更多分享请看:[http://www.cherylgood.cn](http://www.cherylgood.cn)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值