目录:大致内容
- CoordinatorLayout是什么?如何使用?
- 他是如何实现这个效果的呢?
- 其他的一些标签的作用是什么?
- 监听器如何使用。
效果图
这个效果,大家都知道如何实现:在向上滑动的时候隐藏一个图片,下滑的时候显示出来。但你可能没有研究过CoordinatorLayout里面的一些细节,这篇文章我们就来了解一下他们里面的每个标签。

先贴一下实现效果的代码。
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:orientation="vertical"
app:elevation="0dp">
<!--会折叠-->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:elevation="0dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="150dp"
android:adjustViewBounds="true"
android:src="@drawable/setting_icon" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="100dp" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
<!--不折叠-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tablayout"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="100dp"
android:adjustViewBounds="true"
android:src="@drawable/setting_icon" />
</com.google.android.material.appbar.AppBarLayout>
<!--跟在AppBarLayout最后一个控件后面,比如tablayout-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!--固定在顶部-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/toolbarSpace"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/white"
android:adjustViewBounds="true"
android:src="@drawable/setting_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>
kt
/**
* - 文件描述 : 测试fragment
*/
@AndroidEntryPoint
class TodoListFragment : BaseFragment<HomeFragmentTodolistBinding, SharedViewModel>() {
override val mViewModel: SharedViewModel by activityViewModels()
override fun createVB() = HomeFragmentTodolistBinding.inflate(layoutInflater)
override fun HomeFragmentTodolistBinding.initListener() {
}
@RequiresApi(Build.VERSION_CODES.O)
override fun HomeFragmentTodolistBinding.initView() {
// 生成100条数据
val dataList = generateDataList(100)
// 设置适配器
val adapter = DataAdapter(dataList)
rv1.adapter = adapter
rv1.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL,false)
// 添加分割线
rv1.addItemDecoration(
DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
)
mBinding.appBarLayout.addOnOffsetChangedListener { _, verticalOffset ->
val titleHeight = mBinding.banner.height.toFloat() - mBinding.toolbarSpace.height
val alpha = if (abs(verticalOffset) >= titleHeight) 1f else abs(verticalOffset) / titleHeight
mBinding.toolbarSpace.alpha = alpha
mBinding.banner.alpha =
if (abs(verticalOffset) >= titleHeight) 0f else 1 - abs(verticalOffset) / titleHeight
}
}
override fun onDestroyView() {
super.onDestroyView()
}
override fun initObserve() {
lifecycleScope.launch {
}
}
override fun initRequestData() {
}
private fun generateDataList(count: Int): List<DataItem> {
val items = mutableListOf<DataItem>()
val titles = listOf(
"项目", "任务", "功能", "模块", "组件",
"元素", "单元", "部分", "区块", "片段"
)
val descriptions = listOf(
"这是一个重要的数据项",
"请查看此项目的详细信息",
"点击查看更多相关内容",
"此条目包含有价值的信息",
"数据来源于可靠的来源",
"定期更新以保证准确性",
"设计用于优化用户体验",
"包含多种实用功能",
"支持多种交互方式",
"适用于各种使用场景"
)
for (i in 1..count) {
val title = "${titles[i % titles.size]} ${i}"
val description = "${descriptions[i % descriptions.size]} - ID: $i"
items.add(DataItem(i, title, description))
}
return items
}
}
- CoordinatorLayout
- AppBarLayout
- CollapsingToolbarLayout
那么他是如何实现这个效果的呢?我们先看看的每个标签的含义。
一、CoordinatorLayout
CoordinatorLayout
最适合用于需要多个视图协同响应同一交互事件(尤其是滚动) 的场景。
最经典的用例。实现 Toolbar
/ AppBarLayout
随着下方 RecyclerView
/ NestedScrollView
/ ViewPager2
的滚动而展开、收起、隐藏或改变外观(通过 CollapsingToolbarLayout
)、
他专门用于简化复杂视图间交互和动画的实现。
1.1 AppBarLayout
AppBarLayout
是 LinearLayout
的一个子类
它的核心功能是响应其兄弟视图(如 RecyclerView
, NestedScrollView
)的滚动事件,并让自身的子视图(如 Toolbar
, TabLayout
, CollapsingToolbarLayout
)根据滚动做出特定的行为反应(比如折叠、隐藏、改变高度等)。
在没有 AppBarLayout
时,要实现工具栏随列表滚动而隐藏,需要手动监听列表的滚动事件并计算、执行工具栏的平移动画。AppBarLayout
通过与 CoordinatorLayout
和 Behavior
的深度集成,将这一切自动化了。
xml
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:orientation="vertical"
app:elevation="0dp">
<!--会折叠-->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:elevation="0dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="150dp"
android:adjustViewBounds="true"
android:src="@drawable/setting_icon" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="100dp" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
<!--不折叠-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tablayout"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="100dp"
android:adjustViewBounds="true"
android:src="@drawable/setting_icon" />
</com.google.android.material.appbar.AppBarLayout>
1.2 CollapsingToolbarLayout
AppBarLayout的里面还有一个CollapsingToolbarLayout是什么?
其实CollapsingToolbarLayout
是一个专门用于实现高级折叠效果 的工具栏布局。它是 FrameLayout
的子类,并且必须是 AppBarLayout
的直接子视图。它可以包含多个子视图(如图片、工具栏),并管理它们在折叠和展开过程中的视觉变化。
也就是,放到CollapsingToolbarLayout标签里面的内容,才会被折叠,比如banner会被折叠。而tablayout是不会被折叠的。
我们还看到一个Toolbar,通常放在 CollapsingToolbarLayout
的底部,这表示在折叠过程中,Toolbar
会一直被"钉"在顶部,不会被滚出屏幕。比如这里我们高度设置为100dp,那么banner比如为150dp,那么只有50会被滚动出屏幕外,如果你想滚动出全部,就将toolbar设置为0即可。
1.3 如何让rv的滚动,也能让这个AppBarLayout的内容跟着往上走呢?Behavior
就需要使用到Behavior了。
xml
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
layout_behavior的作用,协调 RecyclerView
(或其他可滚动视图,如 NestedScrollView
, ViewPager2
) 与 AppBarLayout
之间的滚动行为
-
string/appbar_scrolling_view_behavior
是一个字符串资源引用。在 Material Design 组件库中,它实际指向的是com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior
这个内部类。 -
它是一个
CoordinatorLayout.Behavior
的具体实现。
设置这个 Behavior 是为了实现以下效果:
1.同步滚动联动: 当用户滚动 RecyclerView
时:
-
向上滚动 (内容向上移动):
AppBarLayout
会接收到滚动事件,并根据其子视图(如Toolbar
,CollapsingToolbarLayout
)上设置的layout_scrollFlags
做出响应(如折叠、隐藏)。 -
向下滚动 (内容向下移动):
AppBarLayout
会展开或显示。
2.调整内容位置: 这个 Behavior 会动态计算并设置 RecyclerView
的顶部内边距 (paddingTop
)。这是为了:
-
确保
RecyclerView
的内容初始时不会被AppBarLayout
遮挡。 -
在
AppBarLayout
完全折叠或隐藏后 ,RecyclerView
的内容能够占据整个屏幕空间。 -
在
AppBarLayout
展开过程中 ,RecyclerView
的内容始终紧跟在AppBarLayout
的底部下方,不会出现重叠或间隙。
3.处理嵌套滚动: 它确保 RecyclerView
的滚动事件能够正确地传递给 AppBarLayout
,让 CoordinatorLayout
知道何时该让 AppBarLayout
响应滚动。
其实到这里,我们就可以做一些简单的操作了。接下来,我们需要在介绍一下他的一个监听器,为什么要监听器呢?比如有一个这样的需求,我们需要知道他滚动的距离是多少,然后我们要隐藏或显示一些东西出来。
有了这个监听器,我们就可以实现一些更加复杂,联动的交互效果。比如我们实现一个,当滚动到一定的距离,显示出标题,反之则隐藏。还是拿我们本期的一个例子来看看。
1.4 监听器
kt
// 为AppBarLayout添加滚动偏移量变化监听器
mBinding.appBarLayout.addOnOffsetChangedListener { _, verticalOffset ->
// 计算标题区域的高度(Banner高度 - Toolbar占位空间高度)
val titleHeight = mBinding.banner.height.toFloat() - mBinding.toolbarSpace.height
// 计算Toolbar占位空间的透明度:
// 当滚动距离超过标题高度时完全显示(alpha=1),否则按比例渐变
val alpha = if (abs(verticalOffset) >= titleHeight) 1f else abs(verticalOffset) / titleHeight
mBinding.toolbarSpace.alpha = alpha
// 计算Banner的透明度:
// 当滚动距离超过标题高度时完全隐藏(alpha=0),否则按比例渐变消失
mBinding.banner.alpha =
if (abs(verticalOffset) >= titleHeight) 0f else 1 - abs(verticalOffset) / titleHeight
}
简单来说,当滚动距离超过一定的高度时,toolbarSpace控件的透明度为1,banner为0,反之亦然。
verticalOffset就是可以拿到AppBarLayout 当前的垂直滚动偏移量。