背景
有个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为什么不能下拉刷新,是什么阻碍了它?
- 手指下拉后,
SwipeRefreshLayout无法响应,即根据touch事件分发机制,SwipeRefreshLayout无法消费这个touch事件.SwipeRefreshLayout是ViewGroup,SwipeRefreshLayout要消费及响应这个touch事件,即在onTouchEvent那里去消费,就是说问题就出现在dispatchTouchEvent,onInterceptTouchEvent或onTouchEvent
touch事件分发机制
- 因为在
SwipeRefreshLayout未找到dispatchTouchEvent的override,所以先看onInterceptTouchEvent.经过调试后发现,当将RecyclerView的第一个item的visibilityset成gone后,SwipeRefreshLayout的onInterceptTouchEvent会return false,并且是因为onInterceptTouchEvent里面的canChildScrollUp()return true导致的.onInterceptTouchEvent() return false即无法去到onTouchEvent消费事件
onInterceptTouchEvent里的canChildScrollUp()判断
- 接着分析
canChildScrollUp().如下图,看到主要是三个判断,这个案例看的是第三个mTarget.canScrollVertically(-1);,这里的mTarget指的是viewGroupSwipeRefreshLayout里面的第一个view --RecyclerView.所以接下来就看为什么RecyclerView的canScrollVertically(-1)会returntrue
canChildScrollUp()分析
- 分析
RecyclerView.canScrollVertically(-1).因为在RecyclerView没找到canScrollVertically(),所以在父View继续寻找,最后在View.java找到canScrollVertically().如下图,主要是offset = computeVerticalScrollOffset()
View.canScrollVertically(-1)
- 接着看
RecyclerView.computeVerticalScrollOffset()为什么会返回>0的数.由于mLayout.canScrollVertically()使用的是LinearLayoutMnager,在当前场景下恒为true,所以主要还是看mLayout.computeVerticalScrollOffset(mState).
RecyclerView.computeVerticalScrollOffset()
- 调试可知,将item set为
gone后,mLayout.computeVerticalScrollOffset(mState)还是会返回item的高度.看到这里,了解RecyclerviewView的优化机制(不了解的,请百度GoogleRecyclerView优化机制)的同学应该就知道应该是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的优化机制,主要还是需要了解常用组件的内部机制.










网友评论