作者:Otway
版權(quán):轉(zhuǎn)載請注明出處!
在復雜的業(yè)務場景中,會利用到 NestedScrollView 嵌套好幾個固定的布局來展示內(nèi)容。
在固定的布局中可能存在豎向的列表,并且要求列表完全展開馆截。針對列表中的 Item 還需要賦予位置移動動畫,整個列表收縮動畫及展開動畫蜂莉。
本文針對該場景采取的是嵌套實現(xiàn)蜡娶。
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/colorAccent">
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
在上面的布局中,RecyclerView中是一個列表數(shù)據(jù)的展示映穗,其中包含 位置移動窖张,收縮,展開等操作蚁滋。(PS:上述層級較多宿接,只是為了測試層級對RecyclerView的影響,畢竟復雜場景不會只有一個RecyclerView)
val list = ArrayList<Int>()
list += 1..20
recycler_view.layoutManager = LinearLayoutManager(this)
adapter = Adapter<Int>(list)
recycler_view.adapter = adapter
recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.itemAnimator.addDuration = 1000
recycler_view.itemAnimator.removeDuration = 1000
adapter!!.expand()
這里我們將動畫的時長設置很長辕录,便于觀察睦霎。其中 Adapter 增加了收縮和展開的方法。
private var maxCount = 0
override fun getItemCount(): Int {
return minOf(dataList.size, maxCount)
}
fun expand() {
val count = itemCount // 同 getItemCount()
maxCount = Int.MAX_VALUE
notifyItemRangeInserted(count, itemCount - count)
}
fun collapse() {
val count = itemCount
maxCount = 1
notifyItemRangeRemoved(1, count - itemCount)
}
簡單的配置之后走诞,我們發(fā)現(xiàn)副女。在展開動畫開啟時,RecyclerView 會伸縮到合適的高度以容納 所有的 Item(這里設置了 android:fillViewport="true"
的屬性)蚣旱,然后我們才會看到默認的 add Item 的動畫碑幅。但是,當我們收縮列表的時候姻锁,并沒有觀察到動畫枕赵,給人的感覺是 直接 調(diào)用了 notifyDataSetChanged()
的方法猜欺。下面我們追蹤一下源碼來看看為什么會出現(xiàn)這種問題位隶。
首先是 notifyItemRangeRemoved()
方法
public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
mObservable.notifyItemRangeRemoved(positionStart, itemCount);
}
根據(jù)該條代碼進行追蹤到最終實現(xiàn)的部分,在 RecyclerViewDataObserver
的實現(xiàn)中开皿,我們找到了具體的實現(xiàn)邏輯涧黄。
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
mHasFixedSize
字段的控制是關(guān)鍵所在,默認是false赋荆,所以直接進行了 requestLayout()
的操作笋妥,導致RecyclerView的高度直接變化到最小。
個人解決方案:
- 如果列表的初始狀態(tài)為完全展開狀態(tài)窄潭〈盒可以通過測量第一個Item高度,以及總高度
recycler_view.post {
val viewFirst = recycler_view.layoutManager.findViewByPosition(0)
firstItemHeight = viewFirst!!.height
totalHeight = recycler_view.height
}
//調(diào)用 adapter.collapse()或者 expand()的時候 調(diào)用 下面的方法。
height(recycler_view, totalHeight.toFloat(), firstItemHeight.toFloat(), 1000, null)
只需要在收縮時 調(diào)用 height 的動畫即可月帝,展開也可以調(diào)用躏惋。
fun height(view: View, from: Float, to: Float, duration: Int, animatorListener: Animator.AnimatorListener?): ValueAnimator {
val animator = ValueAnimator.ofFloat(from, to)
animator.duration = duration.toLong()
if (animatorListener != null) {
animator.addListener(animatorListener)
}
animator.addUpdateListener { animation ->
if (view.layoutParams != null) {
val lp = view.layoutParams
val aFloat = animation.animatedValue as Float
lp.height = aFloat.toInt()
view.layoutParams = lp
}
}
animator.start()
return animator
}
- 通過設置
mHasFixedSize
屬性 來達到目的
if (item.itemId == R.id.collapse) {
recycler_view.setHasFixedSize(true)
it.collapse()
recycler_view.postDelayed({
recycler_view.setHasFixedSize(false)
recycler_view.requestLayout()
}, recycler_view.itemAnimator.addDuration)
} else {
recycler_view.setHasFixedSize(false)
it.expand()
}