【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子

目录:大致内容

  1. CoordinatorLayout是什么?如何使用?
  2. 他是如何实现这个效果的呢?
  3. 其他的一些标签的作用是什么?
  4. 监听器如何使用。

效果图

这个效果,大家都知道如何实现:在向上滑动的时候隐藏一个图片,下滑的时候显示出来。但你可能没有研究过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
    }
}
  1. CoordinatorLayout
  2. AppBarLayout
  3. CollapsingToolbarLayout

那么他是如何实现这个效果的呢?我们先看看的每个标签的含义。

一、CoordinatorLayout

CoordinatorLayout最适合用于需要​​多个视图协同响应同一交互事件(尤其是滚动)​​ 的场景。

最经典的用例。实现 Toolbar/ AppBarLayout随着下方 RecyclerView/ NestedScrollView/ ViewPager2的滚动而展开、收起、隐藏或改变外观(通过 CollapsingToolbarLayout)、

他专门用于简化复杂视图间交互和动画的实现。

1.1 AppBarLayout

AppBarLayoutLinearLayout的一个子类

它的核心功能是​​响应其兄弟视图(如 RecyclerView, NestedScrollView)的滚动事件,并让自身的子视图(如 Toolbar, TabLayout, CollapsingToolbarLayout)根据滚动做出特定的行为反应​​(比如折叠、隐藏、改变高度等)。

在没有 AppBarLayout时,要实现工具栏随列表滚动而隐藏,需要手动监听列表的滚动事件并计算、执行工具栏的平移动画。AppBarLayout通过与 CoordinatorLayoutBehavior的深度集成,将这一切自动化了。

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 当前的垂直滚动偏移量。

相关推荐
用户20187928316713 小时前
Android黑夜白天模式切换原理分析
android
芦半山14 小时前
「幽灵调用」背后的真相:一个隐藏多年的Android原生Bug
android
卡尔特斯14 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
ace望世界14 小时前
安卓的ViewModel
android
ace望世界14 小时前
kotlin的委托
android
CYRUS_STUDIO17 小时前
一文搞懂 Frida Stalker:对抗 OLLVM 的算法还原利器
android·逆向·llvm
zcychong17 小时前
ArrayMap、SparseArray和HashMap有什么区别?该如何选择?
android·面试
CYRUS_STUDIO17 小时前
Frida Stalker Trace 实战:指令级跟踪与寄存器变化监控全解析
android·逆向
ace望世界1 天前
android的Parcelable
android