在复杂的业务场景中,会利用到 NestedScrollView 嵌套好几个固定的布局来展示内容。
在固定的布局中可能存在竖向的列表,并且要求列表完全展开。针对列表中的 Item 还需要赋予位置移动动画,整个列表收缩动画及展开动画。
情景说明
本文针对该场景采取的是嵌套实现。
1 | <android.support.v4.widget.NestedScrollView |
在上面的布局中,RecyclerView中是一个列表数据的展示,其中包含 位置移动,收缩,展开等操作。(PS:上述层级较多,只是为了测试层级对RecyclerView的影响,毕竟复杂场景不会只有一个RecyclerView
1 | val list = ArrayList<Int>() |
这里我们将动画的时长设置很长,便于观察。其中 Adapter 增加了收缩和展开的方法。
1 | private var maxCount = 0 |
简单的配置之后,我们发现。在展开动画开启时,RecyclerView 会伸缩到合适的高度以容纳 所有的 Item(这里设置了 android:fillViewport="true"
的属性),然后我们才会看到默认的 add Item 的动画。但是,当我们收缩列表的时候,并没有观察到动画,给人的感觉是 直接 调用了 notifyDataSetChanged()
的方法。下面我们追踪一下源码来看看为什么会出现这种问题。
首先是 notifyItemRangeRemoved()
方法
1 | public final void notifyItemRangeRemoved(int positionStart, int itemCount) { |
根据该条代码进行追踪到最终实现的部分,在 RecyclerViewDataObserver
的实现中,我们找到了具体的实现逻辑。
1 |
|
mHasFixedSize
字段的控制是关键所在,默认是false,所以直接进行了 requestLayout()
的操作,导致RecyclerView的高度直接变化到最小。
个人解决方案
- 如果列表的初始状态为完全展开状态。可以通过测量第一个Item高度,以及总高度
1 | recycler_view.post { |
只需要在收缩时 调用 height 的动画即可,展开也可以调用。
1 | fun height(view: View, from: Float, to: Float, duration: Int, animatorListener: Animator.AnimatorListener?): ValueAnimator { |
- 通过设置
mHasFixedSize
属性 来达到目的
1 | if (iem.itemId == R.id.collapse) { |