滚动是所有客户端都很常用的交互,毕竟屏幕就这么大,显示的内容往往非常多。

Android 开发中,新手常常会使用 ScrollView 来实现滚动的效果 —— Which is not wrong。

随着需求的增加,可能会需要做一个能滚动的列表,你马上就能反应过来,RecyclerView 或者 ListView —— Which is also correct。

需求再增加,需要做一个如同各电商平台一样,上半部分是某种固定形式的布局,下方是列表的可滚动的效果:

UI 图

新手的直觉是:ScrollView 内嵌套 RecyclerView 或者 ListView —— Which might be able to work —— But definitely not right。

我用 ScrollViewRecyclerView 简单写一下上面这个布局:

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!-- 网格布局 -->
        <GridLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:columnCount="3"
            android:rowCount="2">
            ...
        </GridLayout>

        <!-- 展示列表 -->
        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</ScrollView>

实际上会得到下面这种效果:

滚动冲突

不难看出,RecyclerView 上方的网格布局在跟随 ScrollView 滚动,但 RecyclerView 却由于自身能够滚动的特性,只能在自己所处的位置滚动,而不能跟随 ScrollView 一起滚动。

这其实是一个滚动冲突的问题,解决办法也有很多,初学者遇到这个问题的时候往往是因为不知应如何描述问题而找不到答案。

其实关于 ScrollView 的 Android 官方文档中有专门提到这个问题:

Never add a RecyclerView or ListView to a scroll view. Doing so results in poor user interface performance and a poor user experience.

同时,文档还提供了解决方案:

For vertical scrolling, consider NestedScrollView instead of scroll view which offers greater user interface flexibility and support for the material design scrolling patterns.

NestedScrollView 是 Android 提供的专门解决嵌套滚动的容器,「Nested」其实就是嵌套的意思,它的使用方式和 ScrollView 基本没什么差别,但它能够支持嵌套滚动的父 View 或子 View

因此,我们只要简单替换,就可以解决上面的问题:

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!-- 网格布局 -->
        <GridLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:columnCount="3"
            android:rowCount="2">
            ...
        </GridLayout>

        <!-- 展示列表 -->
        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

效果如下:

正常滚动

虽然本文使用 RecyclerView 作为讲解,但如同文档中所说,这种方法同时还支持解决 ListView 的嵌套滚动问题。