美文网首页
RecyclerView笔记

RecyclerView笔记

作者: brycegao | 来源:发表于2020-08-09 21:17 被阅读0次

    参考
    基于滑动场景解析RecyclerView的回收复用机制原理
    RecyclerView剖析

    一、 缓存机制
    1.1 scrap缓存

    // 添加
     LayoutManager#onLayoutChildren
           // 删除recyclerview中的子view并保存到scrap
           detachAndScrapAttachedViews(recycler);
           // 从4级缓存中查找viewholder后执行addView
    // 删除
     RecyclerView#dispatchLayoutStep3
          // 重置scrap,即清空
          mLayout.removeAndRecycleScrapInt(mRecycler);     
    

    提示:mAttachedScrap和mChangedScrap只用在layout过程中的临时变量, 当recyclerview布局完成后(即STATE_IDLE后)scrap是空数组;

    1.2 mCachedViews
    添加到RecyclerView时不用执行onBindViewHolder函数, 默认大小是2; 从安卓5开始添加了Prefetch功能, 如果使用LinearLayoutManager则大小增1; 如果使用瀑布流StaggeredGridLayoutManager则增加span的个数(例如2列瀑布流则增2)。
    mCachedView数量 = mRequestedCacheMax + prefetch数量

    在做滑动性能调优时可以增加该数组长度, 减少onBindViewHolder的执行次数;

    二、瀑布流
    瀑布流最为复杂, 坑也最多。
    常见顶部留白和item抖动是由于viewholder在layout时和实际高度有差别;
    (百度能搜到一堆,不再赘述)

    1、 在onBindViewHolder中设置itemView的宽度和高度为固定值;
    2、 设置GAP_HANDLING_NONE
    3、 取消默认动画, RecyclerView#setItemAnimator(null);
    4、 滑动到顶部时执行invalidateSpanAssignments, 其实就是清空mLazyLookupSpan并requestLayout。
    

    三、布局流程
    RecyclerView#setAdapter
    RecyclerView.Adapter#notifyDataSetChanged
    实际上都是修改标志位,然后执行requestLayout()。 等到下一个Choreographer触发的doTraversal函数;

    dispatchLayoutStep1:  preLayout阶段, 会缓存动画信息到mViewInfoStore并可能执行一次mLayout.onLayoutChildren
    dispatchLayoutStep2: 真正的布局阶段, 肯定执行mLayout.onLayoutChildren函数;
    dispatchLayoutStep3:postLayout阶段,执行mViewInfoStore中的动画并清空scrap数组, 执行mLayout.onLayoutComplete
    

    tip:重载LayoutManager#onLayoutComplete函数表示布局已完成;

    四、瀑布流缓存
    mLazySpanLookup保存每个item在第几列, 从而滑动列表后记录以前的位置并在重新显示时直接使用。否则使用getNextSpan函数计算出在哪一列;
    mSpans数组, 元素个数等于列数。 保存每一列的view和开始、结束座标, 从而在每一列添加view时从mSpans中拿到以前的位置并添加。

    mSpans是瀑布流的大坑, 这意味着notifyDataSetChanged后并不会从顶部开始添加view, 而是从mSpans缓存的位置添加。
    下拉刷新时添加滑动位置, 其实就是设置参数并在一次layout中完成操作;

    notifyDateSetChanged
    RecyclerView#scrollToPosition(0);    
    
    
                    // Child is not visible. Set anchor coordinate depending on in which direction
                    // child will be visible.
                    anchorInfo.mPosition = mPendingScrollPosition;
                    if (mPendingScrollPositionOffset == INVALID_OFFSET) {
                        final int position = calculateScrollDirectionForPosition(
                                anchorInfo.mPosition);
                        anchorInfo.mLayoutFromEnd = position == LayoutState.LAYOUT_END;
                        anchorInfo.assignCoordinateFromPadding();
                    } else {
                        anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
                    }
                    anchorInfo.mInvalidateOffsets = true;
    
        // 执行scrollToPosition(0)后mInvalidateOffsets为true, 并重置了mSpan
        if (getChildCount() > 0 && (mPendingSavedState == null
                || mPendingSavedState.mSpanOffsetsSize < 1)) {
            if (anchorInfo.mInvalidateOffsets) {
                for (int i = 0; i < mSpanCount; i++) {
                    // Scroll to position is set, clear.
                    mSpans[i].clear();
                    if (anchorInfo.mOffset != INVALID_OFFSET) {
                        mSpans[i].setLine(anchorInfo.mOffset);
                    }
                }
    

    五、神坑
    瀑布流放开item动画后出现诡异问题如被删除的item再次显示到recyclerview(上下滑动后刷新为正确的数据)、间距各种不对。
    解决措施:在onScrollListener里的statechanged函数里判断当前正在itemanimation动画则结束动画;

    常见问题:
    1、滑动列表时先删除再添加view;
    2、recyclerview使用观察者模式, 更新各种标志位后调用requestLayout; requestLayout函数可以调用多次, 其实就是设置标志位;
    3、瀑布流滑动一段距离后执行notifyDataSetChanged时按照mSpans的位置进行layout。
    4、itemdecoration是在RecyclerView的onDraw函数中绘制的。
    后续抽时间补上流程图、示意图。

    相关文章

      网友评论

          本文标题:RecyclerView笔记

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