43、Material Design之CollapsingToolbarLayout:折叠工具栏、与NestedScrollView配合

折叠工具栏,说白了就是让标题栏能跟着页面滚动一起动起来。你刷微博、看知乎的时候,那个慢慢缩上去的标题栏,就是它。

我记得刚接触这个控件时,觉得它就是个花架子。直到有一次做电商详情页,产品图需要占大屏,但用户往下翻时又得让标题栏露出来——嗯,这时候CollapsingToolbarLayout就成了唯一解。

核心原理:一个能折叠的容器

CollapsingToolbarLayout是CoordinatorLayout的“孩子”。它包裹着Toolbar,通过监听NestedScrollView的滚动事件,来决定自己是展开还是折叠。

说白了,它就是个带动画的容器。你给它一个起始高度,一个结束高度,剩下的交给系统。

关键点:CollapsingToolbarLayout必须放在AppBarLayout里面,AppBarLayout再放在CoordinatorLayout里面。这三层嵌套,少一层都不行。

实战:一个完整的折叠工具栏

先看布局文件。我个人习惯把结构写清楚,注释标明白,免得三个月后自己都看不懂。

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:title="商品详情"
            app:expandedTitleGravity="bottom|center_horizontal"
            app:collapsedTitleGravity="center">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/product_banner"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7" />

            <androidx.appcompat.widget.Toolbar
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

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

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="这里是商品详情内容..."
                android:textSize="16sp"
                android:lineSpacingExtra="6dp" />

            <!-- 更多内容 -->

        </LinearLayout>

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

三个核心属性,搞懂就入门了

属性 作用 我的建议
app:layout_collapseMode 子视图的折叠模式 图片用parallax,Toolbar用pin
app:contentScrim 折叠后的背景色 用主题色,过渡自然
app:expandedTitleGravity 展开时标题位置 底部居中比较常见

避坑指南:我曾经踩过的三个坑

第一个坑:NestedScrollView必须加behavior。

我曾经忘了写app:layout_behavior="@string/appbar_scrolling_view_behavior",结果工具栏死活不折叠。查了半天,原来是NestedScrollView没告诉CoordinatorLayout“我要滚动”。

第二个坑:CollapsingToolbarLayout的高度。

如果你给AppBarLayout设了固定高度,CollapsingToolbarLayout就会在这个范围内折叠。我建议用wrap_content或者match_parent,别写死。

第三个坑:Toolbar的标题冲突。

CollapsingToolbarLayout自己有app:title,Toolbar也有setTitle()。如果你两个都设了,会出现标题重叠。我的做法是:只用CollapsingToolbarLayout的title,Toolbar的title留空。

注意:CollapsingToolbarLayout只支持NestedScrollView、RecyclerView、NestedScrollView的子类。普通的ScrollView不行,因为它不发送嵌套滚动事件。

折叠模式详解:三种模式怎么选

每个子视图都可以设置layout_collapseMode,有三种可选:

  • pin:固定在顶部不动。Toolbar用这个,保证标题栏一直在。
  • parallax:视差滚动,移动速度比滚动慢。图片用这个,有层次感。
  • off:不参与折叠,正常滚动。

你想想看,如果图片也用pin,那折叠时图片就硬生生被裁掉了,多难看。parallax配合collapseParallaxMultiplier(0.0到1.0),数值越小移动越慢,效果越明显。

代码里的动态控制

有时候我们需要在代码里控制折叠状态。比如用户点了个按钮,让工具栏展开。

CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar);
AppBarLayout appBarLayout = findViewById(R.id.app_bar);

// 展开
appBarLayout.setExpanded(true, true);

// 折叠
appBarLayout.setExpanded(false, true);

// 监听折叠状态
appBarLayout.addOnOffsetChangedListener((appBarLayout1, verticalOffset) -> {
    int totalScrollRange = appBarLayout1.getTotalScrollRange();
    float progress = Math.abs(verticalOffset) / (float) totalScrollRange;
    // progress 0.0 表示完全展开,1.0 表示完全折叠
    // 可以在这里做渐变动画
});

这个progress值特别有用。我在项目里用它做过标题透明度渐变、背景色渐变、甚至图标切换。比如展开时显示大图,折叠后变成小图标——全靠这个0到1的数值。

SVG结构图:CollapsingToolbarLayout的层级关系

CollapsingToolbarLayout 层级结构 CoordinatorLayout AppBarLayout CollapsingToolbarLayout ImageView (parallax) Toolbar (pin) 其他视图 (off) NestedScrollView (behavior: appbar_scrolling_view_behavior)

什么时候用?什么时候别用?

折叠工具栏不是万能的。我总结了几条经验:

  • 适合用:详情页、个人主页、文章阅读页。这些页面需要展示大图或大标题,又需要快速访问工具栏。
  • 不适合用:列表页、设置页、表单页。这些页面内容密集,折叠工具栏反而占空间。

小技巧:如果你想让折叠更平滑,可以给CollapsingToolbarLayout设置app:contentScrimapp:statusBarScrim。前者是内容区域的遮罩色,后者是状态栏的遮罩色。两个都设,过渡效果才完整。

性能优化:别让动画卡顿

折叠工具栏涉及大量属性动画。如果页面里有高清大图,滚动时可能会掉帧。我的做法是:

  1. 图片用WebP格式,压缩到合适分辨率。
  2. 避免在onOffsetChanged里做耗时操作。
  3. 如果图片需要缩放,用ImageViewscaleType,别用自定义动画。

嗯,说到这里,其实CollapsingToolbarLayout的核心就这些。它不复杂,但细节多。你只要记住三层嵌套、三种模式、一个behavior,基本就能应付大部分场景了。

我在项目里用过不下十次,每次都能玩出新花样。比如配合ViewPager做轮播图折叠,或者配合TabLayout做分类页——只要你理解了它的滚动机制,创意空间其实很大。


公众号:蓝海资料掘金营,微信deep3321