美文网首页
RecyclerView + NestedScrollView

RecyclerView + NestedScrollView

作者: 弥宣 | 来源:发表于2018-01-07 19:16 被阅读689次

    作者:Otway
    版权:转载请注明出处!

    在复杂的业务场景中,会利用到 NestedScrollView 嵌套好几个固定的布局来展示内容。
    在固定的布局中可能存在竖向的列表,并且要求列表完全展开。针对列表中的 Item 还需要赋予位置移动动画,整个列表收缩动画及展开动画。

    本文针对该场景采取的是嵌套实现。

    <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中是一个列表数据的展示,其中包含 位置移动,收缩,展开等操作。(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)
        }
    

    简单的配置之后,我们发现。在展开动画开启时,RecyclerView 会伸缩到合适的高度以容纳 所有的 Item(这里设置了 android:fillViewport="true" 的属性),然后我们才会看到默认的 add Item 的动画。但是,当我们收缩列表的时候,并没有观察到动画,给人的感觉是 直接 调用了 notifyDataSetChanged() 的方法。下面我们追踪一下源码来看看为什么会出现这种问题。

    首先是 notifyItemRangeRemoved() 方法

            public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
                mObservable.notifyItemRangeRemoved(positionStart, itemCount);
            }
    

    根据该条代码进行追踪到最终实现的部分,在 RecyclerViewDataObserver 的实现中,我们找到了具体的实现逻辑。

           @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 字段的控制是关键所在,默认是false,所以直接进行了 requestLayout() 的操作,导致RecyclerView的高度直接变化到最小。

    个人解决方案:

    • 如果列表的初始状态为完全展开状态。可以通过测量第一个Item高度,以及总高度
            recycler_view.post {
                val viewFirst = recycler_view.layoutManager.findViewByPosition(0)
                firstItemHeight = viewFirst!!.height
                totalHeight = recycler_view.height
            }
            //调用 adapter.collapse()或者 expand()的时候 调用 下面的方法。
           height(recycler_view, totalHeight.toFloat(), firstItemHeight.toFloat(), 1000, null)
    
    

    只需要在收缩时 调用 height 的动画即可,展开也可以调用。

    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()
                    }
    

    相关文章

      网友评论

          本文标题:RecyclerView + NestedScrollView

          本文链接:https://www.haomeiwen.com/subject/zffmnxtx.html