美文网首页
Bug分析:因RecyclerView而SwipeRefresh

Bug分析:因RecyclerView而SwipeRefresh

作者: Edgar_Ng | 来源:发表于2020-08-30 14:32 被阅读0次

    背景

    有个RecyclerView的列表在外面套了一层SwipeRefreshLayout下拉刷新的组件,RecyclerView 以item的方式在position=0的地方set了一个header.之后小伙伴在header处理完一些业务后将此header.visibility = gone以实现将header隐藏/移除的效果.

    问题:在隐藏/移除header后,SwipeRefreshLayout就无法实现下拉刷新.

    原因:原因还挺蠢的,不能这样移除RecyclerView的item,不过这里面也有组件化的原因加了难度,让小伙伴一时没留意这个知识点.

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <androidx.recyclerview.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="gone"/>
    
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    

    分析

    • 现象
      隐藏header后无法下拉刷新
    • 问题描述
      RecyclerView的第一个item的visibilityset成gone后无法触发SwipeRefreshLayout的下拉刷新
    • 问题分析
      SwipeRefreshLayout为什么不能下拉刷新,是什么阻碍了它?
    1. 手指下拉后,SwipeRefreshLayout无法响应,即根据touch事件分发机制,SwipeRefreshLayout无法消费这个touch事件.SwipeRefreshLayoutViewGroup,SwipeRefreshLayout要消费及响应这个touch事件,即在onTouchEvent那里去消费,就是说问题就出现在dispatchTouchEvent ,onInterceptTouchEventonTouchEvent
      touch事件分发机制
    2. 因为在SwipeRefreshLayout未找到dispatchTouchEventoverride,所以先看onInterceptTouchEvent.经过调试后发现,当将RecyclerView的第一个item的visibilityset成gone后,SwipeRefreshLayoutonInterceptTouchEventreturn false,并且是因为onInterceptTouchEvent里面的canChildScrollUp() return true导致的.onInterceptTouchEvent() return false即无法去到onTouchEvent消费事件
      onInterceptTouchEvent里的canChildScrollUp()判断
    3. 接着分析canChildScrollUp().如下图,看到主要是三个判断,这个案例看的是第三个mTarget.canScrollVertically(-1);,这里的mTarget指的是viewGroup SwipeRefreshLayout里面的第一个view --RecyclerView.所以接下来就看为什么RecyclerViewcanScrollVertically(-1)会return true
      canChildScrollUp()分析
    4. 分析RecyclerView.canScrollVertically(-1).因为在RecyclerView没找到canScrollVertically(),所以在父View继续寻找,最后在View.java找到canScrollVertically().如下图,主要是offset = computeVerticalScrollOffset()
      View.canScrollVertically(-1)
    5. 接着看RecyclerView.computeVerticalScrollOffset()为什么会返回>0的数.由于mLayout.canScrollVertically()使用的是LinearLayoutMnager,在当前场景下恒为true,所以主要还是看mLayout.computeVerticalScrollOffset(mState).
      RecyclerView.computeVerticalScrollOffset()
    6. 调试可知,将item set为gone后,mLayout.computeVerticalScrollOffset(mState)还是会返回item的高度.看到这里,了解RecyclerviewView的优化机制(不了解的,请百度Google RecyclerView优化机制)的同学应该就知道应该是item直接set为gone后,没有通知到layoutManager所导致的.那么我们应该怎么通知layoutManager呢?

    解决方案

    通过上面的问题分析,可以知道是因为item直接set为gone后,没有通知到layoutManager所导致的.所以我们可以这样做.

    //清除data里面对应的item
    adapter.data.remove(itemPosition)
    //通知adapter去更新recyclerview的item remove状态,这里会有动画显示
    recyclerView.adapter.notifyItemRemoved(itemPosition)
    

    总结

    其实这里犯的错还是蛮低级的,改动也不难.写这篇文章更多是表达如何去排查问题,一开始会以为是SwipeRefreshLayout有bug,其实也不算是,只是RecyclerView的优化机制,主要还是需要了解常用组件的内部机制.

    引用

    1. 图解 Android 事件分发机制
    2. Android在线源码

    相关文章

      网友评论

          本文标题:Bug分析:因RecyclerView而SwipeRefresh

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