Android之RecyclerView核心要点解析

1 RecyclerView的ViewHolder

RecyclerView是我们在开发中经常会用到的控件,也是面试中经常会问到。RecyclerView相对ListView在用法上,最明显的一点就是它会强制我们使用ViewHolder。那么,ViewHolder到底是什么呢,它和我们常说的复用有没有关系呢。

其实,ViewHolder和复用是没有关系的。在ListView中,我们是把findViewById()的过程放在ViewHolder中,然后再把ViewHolder存在convertView的Tag。然后通过getTag()获取存储的ViewHolder。而这个我们存储的ViewHolder是已经findViewById()的。所以不需要重新findViewById()。

所以,我们知道了ViewHolder是用来解决重复绑定id这个过程,因为findViewById()是一个比较耗费资源的过程。

2 RecyclerView的缓存机制

我们常说RecyclerView有着比ListView更好的性能。其中,一个重要的原因就是因为RecyclerView有着比ListView更好的缓存机制。想对RecyclerView的缓存有更深刻的了解,我们先了解一下ListView的缓存。

ListView共有两级缓存,它是通过RecycleBin来管理,先是从Activie View获取,如果Activie View获取不到,就从Scrap View获取。Activie View就是在屏幕中显示的Item,Scrap View就是在屏幕外的Item。为什么屏幕显示的Item也需要缓存呢,因为android的屏幕是不断刷新的,它每次刷新的时候把最新的View渲染到屏幕上,它就不需要重新绑定数据,通过Activie View就可以实现。Scrap View是负责重新绑定数据的,移出屏幕的Item会被放入Scrap View中。如果Scrap View中也找不到,就由我们在getView()中创建新的Item。

RecyclerView相比ListView,缓存机制会更为复杂。RecyclerView总共有四层缓存,是通过Recycler来管理。第一层是Scrap,第二层是Cache,第三层是ViewCacheExtension,第四层是RecycledViewPool。

Scrap是RecyclerView的一级缓存,但是它又和ListView的二级缓存名字一样。它在屏幕里面的,是可以直接复用的,它是通过position来找到对应的View的。刚出屏幕的几个Item,它会放在第二层的cache里面。它和第一层的Scrap是差不多,它是可以直接复用的,不需要重新绑定,而且它也是用position来找的。它和Scrap不同的是,它是已经出了屏幕的.

第三层叫ViewCacheExtension,是用户自己定义的Cache策略。这个在实际中用的比较少。最后一个是RecycledViewPool,它里面的数据需要重新绑定的,它里面是通过viewType来找到数据。它不走onCreatViewHolder,但是会走onBindViewHolder。

3 RecyclerView的性能优化策略

1 onCreateViewHolder中设置点击监听,而不是在onBindViewHolder中设置,因为onBindViewHolder在一个item中是有可能调用多次的,在里面设置点击事件有可能导致重复创建对象。

2 RecyclerView.setHasFixedSize(true),如果 Adapter 的数据变化不会导致 RecyclerView的大小变化的时候,我们应该调用这个方法。它可以避免RecyclerView重新计算大小。

3 当多个 RecyclerView的Item是一样时,可以共用RecycledViewPool。

RecyclerView.RecycledViewPool recycledViewPool =new RecyclerView.RecycledViewPool();

recyclerView1.setRecycledViewPool(recycledViewPool);

recyclerView2.setRecycledViewPool(recycledViewPool);

4 RecyclerView的分割线

ListView设置分割线是比较简单的,可以直接在xml中设置它的颜色和粗细。RecyclerView相比来说,设置分给先是比较复杂的。简单的分割线可以用下面的方式设置。

RecyclerView recyclerView = new RecyclerView(this);
recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

为什么调用上述代码就可以设置分割线呢,我们来看一下DividerItemDecoration的源码。

public void onDraw(Canvas c, RecyclerView parent, State state) {
    if (parent.getLayoutManager() != null && this.mDivider != null) {
        if (this.mOrientation == 1) {
            this.drawVertical(c, parent);
        } else {
            this.drawHorizontal(c, parent);
        }

    }
}

我们可以看到在DividerItemDecoration中有onDraw()这个方法,但是它本身并不会调用,而是会由RecyclerView来调用,具体调用如下。

public void onDraw(Canvas c) {
    super.onDraw(c);
    int count = this.mItemDecorations.size();

    for(int i = 0; i < count; ++i) {
        ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
    }

}

DividerItemDecoration的onDraw()方法中,会调用传入的参数调用drawVertical()或drawHorizontal(),我们选择其中一个,看一下drawVertical的源码。

private void drawVertical(Canvas canvas, RecyclerView parent) {
    canvas.save();
    int left;
    int right;
    if (parent.getClipToPadding()) {
        left = parent.getPaddingLeft();
        right = parent.getWidth() - parent.getPaddingRight();
        canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
    } else {
        left = 0;
        right = parent.getWidth();
    }

    int childCount = parent.getChildCount();

    for(int i = 0; i < childCount; ++i) {
        View child = parent.getChildAt(i);
        parent.getDecoratedBoundsWithMargins(child, this.mBounds);
        int bottom = this.mBounds.bottom + Math.round(child.getTranslationY());
        int top = bottom - this.mDivider.getIntrinsicHeight();
        this.mDivider.setBounds(left, top, right, bottom);
        this.mDivider.draw(canvas);
    }

    canvas.restore();
}

可以看到,这是个和绘制相关的方法,中间有一个遍历的过程。其实,这个就是绘制那条分割线。后面还有一个关键的方法,叫getItemOffsets()。offset都是和偏移有关的,所以这段代码是设置偏移的。当然,添加分割线整体代码还是非常复杂的,还要和RecyclerView的源码联系起来。这里只分析几个重要的方法,其它就不一一细说了

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    if (this.mDivider == null) {
        outRect.set(0, 0, 0, 0);
    } else {
        if (this.mOrientation == 1) {
            outRect.set(0, 0, 0, this.mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, this.mDivider.getIntrinsicWidth(), 0);
        }

    }
}

RecyclerView的分割线为什么要做的那么复杂呢,为什么不做的像ListView那么简单呢。添加分割线的这个方法名叫addItemDecoration(),翻译成中文就是添加一个装饰。其实它的目的不只是做分割线这么简单,一些对Item的修改、装饰等都可以通过这个方法来实现。举个例子,比如你想让某些图片的背景变得昏暗一些,可以在上面绘制多一层。或者你想自己自己定义分割线的样式,分割线不是百分百长度,而是百分之七八十长度的,也可以自己设置。这些都可以通过addItemDecoration()这个方法来实现

5 总结

至此,RecyclerView的一些较难的技术已经分析完了。RecyclerView虽然容易上手,性能也比ListView更好,但是它的很多原理,很多人应该还不是完全理解,特别是和ListView的缓存对比这一块,经常在面试中会被问到,希望这篇文章可以帮助到大家。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值