Android UI:为什么别人的UI看起来很有科技感,并且动画,交互非常的丝滑和顺畅,为什么呢?因为我们没有了解MotionLayout

5,679 阅读5分钟

一、前言

为什么别人的UI看起来很有科技感,并且动画,交互非常的丝滑和顺畅,为什么呢?因为我们没有了解MotionLayout。

在这里插入图片描述

二、MotionLayout是什么?

MotionLayout 是 Android 中基于 ConstraintLayout 的高级布局容器,在 ConstraintLayout 2.0 版本中引入的。专门用于管理复杂的视图动画和过渡效果。它通过声明式 XML 或代码定义视图的运动路径、关键帧动画和交互行为,适用于实现高度动态的 UI 交互(如折叠效果、滑动抽屉、复杂转场动画等)。

2.1 特点

1、继承自 ConstraintLayout:完全兼容 ConstraintLayout 的约束系统,可直接在现有布局上扩展动画功能。

2、基于关键帧的动画:通过定义起始和结束状态(ConstraintSet),自动生成平滑过渡动画,支持中间关键帧调整。

3、交互驱动动画:动画可由用户手势(如滑动、拖动)或程序事件实时触发,并支持动态响应(如速度跟踪)。

4、可视化工具支持:Android Studio 提供 Motion Editor,支持图形化设计动画路径和属性变化。

三、快速入门

  1. 添加依赖

确保 build.gradle 中包含最新版本:

dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
  1. 定义 MotionScene(动画规则)

在 res/xml 中创建 motion_scene.xml,定义动画的起始/结束状态和过渡:

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 起始状态 -->
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_marginStart="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>
    </ConstraintSet>

    <!-- 结束状态 -->
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_marginEnd="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>
    </ConstraintSet>

    <!-- 定义过渡动画 -->
    <Transition
        android:id="@+id/transition"
        app:constraintSetStart="@id/start"
        app:constraintSetEnd="@id/end"
        app:duration="1000">
        <!-- 触发条件:点击按钮 -->
        <OnClick
            app:targetId="@id/button"
            app:clickAction="toggle"/>
    </Transition>
</MotionScene>
  1. 在布局中关联 MotionLayout

在 XML 布局文件中使用 MotionLayout,并绑定 MotionScene:

<androidx.constraintlayout.motion.widget.MotionLayout
    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"
    app:layoutDescription="@xml/motion_scene">

    <Button
        android:id="@+id/button"
        android:text="Animate"
        android:backgroundTint="#6200EE"/>

</androidx.constraintlayout.motion.widget.MotionLayout>
  1. 运行效果

点击按钮时,它会从屏幕左侧平滑移动到右侧(反之亦然)。

四、实现上图效果

在这里插入图片描述 这里就省略了tablayout+viewpager的使用,会使用一个简单的textview来代替

4.1、布局

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:layoutDescription="@xml/library_tab_three"
    android:id="@+id/root"
    tools:progress="0"
    android:background="#ccc"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    >

    <TextView
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="@string/library"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/status_bar_parent" />

    <TextView
        android:id="@+id/body"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="到置顶显示"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/header" />
</androidx.constraintlayout.motion.widget.MotionLayout>

4.2、library_tab_three文件

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <!-- 定义过渡动画 -->
    <Transition
        app:constraintSetEnd="@layout/screen_tab_three_end"
        app:constraintSetStart="@layout/screen_tab_three"
        app:duration="350">

<!--
        这是定义基于滑动操作的触发器。
            app:touchAnchorId="@id/box":指定了触摸的锚点视图(@id/box)。用户需要在 box 视图上进行滑动操作才能触发动画。
            app:touchAnchorSide="top":指定了触摸锚点的上边。这里表示触摸锚点视图(box)的侧边。
            app:dragDirection="dragRight":指定了滑动的方向。这里表示当用户向上滑动时触发动画。
-->
        <OnSwipe
            app:dragDirection="dragUp"
            app:touchAnchorId="@id/body"
            app:touchAnchorSide="top"
             />
    </Transition>
</MotionScene>

4.3、screen_tab_three_end:结束的时候

比如我们结束的时候,要隐藏掉音乐库的控件,给这个控件加一个gone

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:layoutDescription="@xml/library_tab_three"
    android:id="@+id/root"
    tools:progress="0"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    >

    <TextView
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="@string/library"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/status_bar_parent"/>

    <TextView
        android:id="@+id/body"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="到置顶显示"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/header" />
</androidx.constraintlayout.motion.widget.MotionLayout>

4.4、screen_tab_three

内容和布局文件没有什么不同。

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:layoutDescription="@xml/library_tab_three"
    android:id="@+id/root"
    tools:progress="0"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    >

    <TextView
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="@string/library"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/status_bar_parent" />

    <TextView
        android:id="@+id/body"
        android:layout_width="0dp"
        android:layout_height="75dp"
        android:gravity="center_vertical"
        android:paddingStart="25dp"
        android:paddingEnd="0dp"
        android:text="到置顶显示"
        android:textColor="#000000"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/header" />
</androidx.constraintlayout.motion.widget.MotionLayout>

运行程序就可以了。

可以看到这里我并没有使用ConstraintSet来作为开始和结束,而是直接把之前的代码复制过来,start是显示,end文件就隐藏了。为什么这样也可以呢?因为它可以推算出来。

OSZAR »