背景
有个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的visibility
set成gone
后无法触发SwipeRefreshLayout
的下拉刷新 - 问题分析
SwipeRefreshLayout
为什么不能下拉刷新,是什么阻碍了它?
- 手指下拉后,
SwipeRefreshLayout
无法响应,即根据touch事件分发机制,SwipeRefreshLayout
无法消费这个touch事件.SwipeRefreshLayout
是ViewGroup
,SwipeRefreshLayout
要消费及响应这个touch事件,即在onTouchEvent
那里去消费,就是说问题就出现在dispatchTouchEvent
,onInterceptTouchEvent
或onTouchEvent
touch事件分发机制 - 因为在
SwipeRefreshLayout
未找到dispatchTouchEvent
的override
,所以先看onInterceptTouchEvent
.经过调试后发现,当将RecyclerView
的第一个item的visibility
set成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
的优化机制,主要还是需要了解常用组件的内部机制.
网友评论