美文网首页
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