NestedScrollView嵌套RecyclerView +
-
背景:
需要做一个同时显示统计图 + 统计表的页面,统计图在上方,统计表在下方,同时统计表数据较多,一页无法显示完成,需要支持横向滚动和纵向滚动。 -
解决方案:
使用NestedScrollView嵌套RecyclerView作为纵向滚动实现,NestedScrollView主要用于展示统计图和一些其他的按钮,RecyclerVIew主要用于展示和加载统计表各项。然后对于RecyclerView中的每一项,用HorizontalScrollView嵌套RecyclerView实现横向同步滚动加载。 -
三个难题:
-
NestedScrollView嵌套RecyclerView无法复用缓存的问题
原因:因为在NestedScrollView的onMeasure
过程中,会将他的child的测量模式设置成MeasureSpec.UNSPECIFIED
,当父布局设置成这个模式时,除非子布局设置了固定长度,否则子视图也会变成MeasureSpec.UNSPECIFIED
。
这个配置会在RecyclerView的onMeasure
的过程中设置到LayoutManager中,然后在onLayout
->onLayoutChildren
->fill
里面会根据(layoutState.mInfinite || remainingSpace > 0)
不断放置item,因为是MeasureSpec.UNSPECIFIED
,所以layoutState.mInfinite
是一直为true
。所以就会将item全部加载出来,不会按需加载回收复用。 -
NestedScrollView嵌套RecyclerView滑动冲突的问题
解决了无法RecyclerView无法复用的问题,就会引出另外一个问题,就是NestedScrollView和RecyclerView的滑动冲突。对于NestedScrollView和RecyclerView的嵌套滑动,原生的实现是,优先将滑动事件交给RecyclerView消费,如果RecyclerView消费不完,才会将剩余的交给NestedScrollView消费,这个不符合需求,所以需要修改。通过覆写NestedScrollView的onNestedPreScroll
,当收到向上滑动的事件时,先将事件交给NestedScrollView消费,当NestedScrollView消费不完,再交回给RecyclerView消费,从而解决滑动冲突的问题。 -
RecyclerView横向同步滚动
RecyclerView本身是支持横向滚动的,但是如果需要批量同步横向滚动的话,需要监听滑动事件,然后同步响应滑动。但是因为RecyclerView只有scrollToPosition
和scrollBy
,不容易做到同步滑动,在参考了别人的实现方案之后,决定通过嵌套HorizontalScrollView,通过监听Scroll事件,然后使用scrollTo
进行滚动,实现同步批量滑动。
-
-
后续优化方向:
1. 对于纵向的RecyclerView的高度可以根据实际返回的item实现自适应高度。
2. 使用自定义View实现横向滚动,减少View的嵌套和和过度绘制。
3. 横向的RecyclerView如果在数据较多的情况下,如何实现复用。 -
其他解决方案:
1. 直接使用CoordinatorLayout
布局,进行联动滚动,但是因为item里面还有RecyclerView,这样多层嵌套会产生滑动冲突,第一次滑动底部RecyclerView的时候,滑动事件被RecyclerView捕获,无法上传到CoordinatorLayout中,导致滑动的是RecyclerView。所以决定还是使用NestedScrollView进行嵌套滚动。并且如果是使用CoordinatorLayout的话,嵌套会更多,在inflate布局的时候,会消耗更多的性能。
2. 将统计图设置为RecyclerView的Header,因为统计图上存在一些交互和数据处理,如果都写到Adapter中,会让Adapter显得臃肿,并且在Adapter里面添加接口到Activity中进行调用,会加深Adapter和Activity的依赖,所以也没有使用这种方案。
3. 横向同步滚动不嵌套ScrollView,直接使用RecyclerView进行滚动,RecyclerView不支持scrollTo
这个方法,使用scrollBy
会产生无限回调,导致堆栈溢出。使用scrollToPosition
需要对滚动值进行计算,计算比较复杂,而且容易出错。
4. 不使用RecyclerView,直接用ScrollView + TextView,这种方案实际上可以考虑,而且性能会比RecyclerView好,但是不便于后续的扩展。
5. 使用自定义的View实现横向滚动,这个是后续考虑的优化方向,可以减少层级嵌套,并且在滚动过程中可以隐藏看不见的View,减少过度绘制,提升绘制效率。但是因为实现较为复杂,当时时间太赶,没有来得及研究和实现。