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

相关推荐
薛晓刚1 分钟前
MySQL的replace使用分析
android·adb
DengDongQi24 分钟前
Jetpack Compose 滚轮选择器
android
stevenzqzq24 分钟前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停33 分钟前
MySQL事务
android·数据库·mysql
朝花不迟暮38 分钟前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq1 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter1 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter1 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse1 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库
美狐美颜sdk1 小时前
Android直播美颜SDK:选择指南与开发方案
android·人工智能·计算机视觉·第三方美颜sdk·视频美颜sdk·人脸美型sdk