Android MotionLayout动画之KeyFrameSet关键帧详解(二)

MotionLayout系列文章

Android | MotionLayout入门级使用教程(一):https://blog.csdn.net/u013700502/article/details/138142228

关键帧KeyFrameSet

motionScene

在 MotionScene 中,KeyFrameSet 是一个用于定义动画关键帧的容器,它可以在动画的特定时间点上对属性进行插值或触发事件,从而控制动画的行为和效果。通过关键帧,可以定义精确的动画轨迹和行为,而不是仅仅依赖 MotionLayout 的默认插值。

keyframe

KeyFrameSet 包含多个关键帧类型,每种类型可以控制不同的属性或触发不同的事件。这些关键帧通过动画的时间轴 (framePosition) 定位。

KeyPosition

KeyPosition

KeyPosition 是一个关键帧类型,用于定义视图在动画路径上的位置和大小变化。它允许通过百分比指定位置、大小、角度等属性,以便在特定的动画帧上对元素进行更精细的控制。以下是常用属性及其含义:

  • framePosition:定义动画帧的位置,表示在动画中的百分比位置。范围为 0-100,例如 framePosition=“50” 表示动画的中点。
  • motionTarget:指定该关键帧所应用的目标视图,通过 ID 进行引用。
  • percentX / percentY:是指在 framePosition 按多大百分比来修改路径(值介于 0.0 到 1.0 之间,允许使用负数值和大于 1 的值)。可以简单理解为:在framePosition,修改motionTarget 的路径,即根据 keyPositionType 确定的坐标移动percentX 或percentY 。
  • keyPositionType:定义关键帧位置的计算方式,keyPositionType 属性用于确定如何根据 percentX 或 percentY 来修改路径。它可以是 parentRelative、pathRelative 或 deltaRelative。

    parentRelative: 相对于父容器的位置来计算关键帧,左上角和右下角分别为(0,0)和(1,1)。
    在这里插入图片描述

deltaRelative: 基于相对于起始点和结束点的相对偏移量来计算路径,起始坐标和终点坐标分别为(0,0)和(1,1)。
deltaRelative

pathRelative: 基于运动路径的百分比位置,从起点连接到终点的方向是X轴,Y轴的正方向是X轴顺时针转90度的方向。
pathRelative

  • percentWidth:表示在宽度上的百分比变化量。若宽度无变化,设置此属性无效,可用来实现逐帧增宽的效果。
  • percentHeight:表示在高度上的百分比变化量。同 percentWidth,若高度无变化则无效。
  • sizePercent:定义元素的整体大小百分比变化(宽度和高度比例一致),是 percentWidth 和 percentHeight 的简化形式。
  • transitionEasing:用于定义在关键帧上应用的缓动效果。可以选择 easeIn, easeOut 等内置类型或自定义曲线。
  • pathMotionArc:定义路径的弧度,如 startVertical, startHorizontal 等。用于创建更加平滑的曲线运动。
  • curveFit:用于曲线拟合,指定如何连接关键帧。通常用于处理更复杂的曲线路径,取值有:spline样条插值、linear线性插值。

视频介绍:https://www.youtube.com/watch?v=3HQMCyAkWTE

KeyAttribute

KeyAttribute 是在特定时间点上动态修改 UI 元素属性的关键帧类型。通过KeyAttribute,可以在动画的某些时刻(关键帧)对元素的多个属性进行变换,例如透明度、旋转、缩放、平移等,从而实现复杂的动画效果。以下是 KeyAttribute 内各字段的含义:

属性详解

1、framePosition:定义了关键帧在动画进度中的位置,以百分比(0 到 100)表示。例如,framePosition=50 表示动画进行到一半时触发该关键帧,framePosition=“25"表示该关键帧会在动画的 25% 处生效。
2、motionTarget:指定关键帧的目标视图或组件,通常使用视图 ID 引用。如:motionTarget=”@+id/my_view" 用于指定应用此关键帧的视图。
3、transitionEasing:控制该关键帧的动画缓动效果,例如线性或非线性变化。示例:transitionEasing=“easeInOut” 表示从平滑启动到渐渐加速,最后减速结束。
4、curveFit:定义了属性在动画中的插值方式,如线性或样条插值。示例:curveFit=“spline” 表示使用样条插值。
5、motionProgress:用于控制 MotionLayout 内的嵌套动画进度,范围从 0 到 1。示例:在某个关键帧时,可以通过 motionProgress=“0.5” 将进度条设为一半。

除上面的属性之外,还可以设置一些View相关的属性,从而对视图的外观和变换进行调整。

android:alpha:设置透明度。android:alpha=“0.5” 表示视图半透明。
android:elevation:控制视图的高度,以增加视图的层次感。示例:android:elevation=“10dp” 表示将视图抬升 10dp。
android:rotation、android:rotationX、android:rotationY:分别控制视图的二维和三维旋转角度。示例:android:rotation=“45” 将视图顺时针旋转 45 度。
android:transformPivotX、android:transformPivotY:控制旋转和缩放的中心点。示例:android:transformPivotX=“50dp” 表示将 X 轴的旋转中心设置在 50dp 处。
transformPivotTarget:指定其他视图作为旋转和缩放的中心点。
transitionPathRotate:视图沿动画路径旋转的角度。示例:transitionPathRotate=“30” 将视图沿路径旋转 30 度。
android:scaleX、android:scaleY:分别设置视图的 X 和 Y 轴缩放。示例:android:scaleX=“1.5” 将视图宽度放大 1.5 倍。
android:translationX、android:translationY:控制视图的 X、Y 方向偏移。示例:android:translationX=“20dp” 表示将视图向右平移 20dp。
android:translationZ:控制视图在 Z 轴上的偏移。

KeyAttribute 提供了灵活的方式去设置动画中的多个关键帧,从而实现复杂且流畅的动画效果。视频介绍:https://www.youtube.com/watch?v=jUm_AkH_mAQ

KeyCycle

KeyCycle在动画过程中做周期性波形来实现动画效果。通过 KeyCycle,可以让属性值以波动的方式进行变化(例如像正弦波或方波),用于产生类似于脉冲或振荡的效果。

属性解释

1、motionTarget:指定应用波动效果的目标视图,通常使用视图的 ID。
2、curveFit:插值方式,决定波形曲线的平滑程度。示例:curveFit=“spline” 表示使用样条插值。
3、framePosition:定义关键帧在动画进度中的位置,以 0 到 100 的百分比表示。示例:framePosition=“50” 表示在动画进度的一半时触发此波动效果。
4、transitionEasing:缓动函数,控制波动动画的速度和流畅度。示例:transitionEasing=“easeInOut”。
5、motionProgress:控制 MotionLayout 内嵌套动画的进度,值从 0 到 1。
6、waveShape:定义波形的形状,可选值包括:
• sin:正弦波
• square:方波
• triangle:三角波
• sawtooth:锯齿波
• reverseSawtooth:反向锯齿波
• cos:余弦
• bounce:弹跳
7、wavePhase:波的初始相位,单位为度数。它控制波形的起始位置。示例:wavePhase=“90” 表示波形从 90 度开始。
8、wavePeriod:波形周期,表示波形重复的频率。值越小,频率越高。示例:wavePeriod=“2” 表示波形每 2 秒完成一个周期。
9、waveOffset:波的偏移量,控制波动效果的初始位置。示例:waveOffset=“0、2” 表示从 0、2 的位置开始波动。
10、waveVariesBy:定义波动变化的方向,可以是 x, y 或 path。示例:waveVariesBy=“x”。
11、 transitionPathRotate:视图沿路径旋转的角度,主要在路径动画中使用。示例:transitionPathRotate=“45” 使视图沿路径旋转 45 度。

其他视图属性,这些属性允许在 KeyCycle 中对视图的多个变换进行波动效果的设置,例如透明度、旋转、缩放和位移等。

android:alpha:透明度变化。
android:elevation:视图的高度。
android:rotation、android:rotationX、android:rotationY:控制二维和三维旋转。
android:scaleX、android:scaleY:控制 X 和 Y 轴缩放。
android:translationX、android:translationY:控制 X 和 Y 方向位移。
android:translationZ:控制 Z 轴方向位移。

视频介绍:https://www.youtube.com/watch?v=qWmU6emSQ5k

KeyTimeCycle

KeyTimeCycle 用于实现基于时间的周期性动画。通过该关键帧,可以对视图的属性(如透明度、旋转、位移等)产生随时间波动的效果,常用于制作振荡或周期性的动画。

属性详解

  1. framePosition:动画进度中的位置,以百分比表示(0-100)。确定波动效果的触发时间点。
  2. motionTarget:指定动画目标视图,通过视图 ID 关联。
  3. transitionEasing:控制缓动效果,定义波形的流畅性。
  4. curveFit:曲线拟合方法,用于控制插值的平滑度和效果(如 spline)。
  5. waveShape:波形的形状,定义周期变化的方式。常用的值包括:
    • sin:正弦波
    • square:方波
    • triangle:三角波
    • sawtooth:锯齿波
    • reverseSawtooth:反向锯齿波
    • cos:余弦
    • bounce:弹跳
    波形形状
  6. wavePeriod:波形周期,控制波形的频率,值越大表示频率越高。
  7. motionProgress:控制嵌套动画的进度,范围为 0 到 1。
  8. waveOffset:波的偏移量,用于设置初始波动位置。
  9. wavePhase:波的相位,定义波的起始角度,以度数表示。
  10. waveDecay:波的衰减时间,单位为毫秒。控制波动效果随时间逐渐减弱,直到小于 1/256。

除上述属性之外,还可以对视图属性添加波动效果,包括透明度、缩放、旋转和位移等:

android:alpha:透明度变化。
android:elevation:视图的高度。
android:rotation、android:rotationX、android:rotationY:控制二维和三维旋转。
android:scaleX、android:scaleY:控制 X 和 Y 轴缩放。
android:translationX、android:translationY:控制 X 和 Y 方向位移。
android:translationZ:控制 Z 轴方向位移。

视频介绍:https://www.youtube.com/watch?v=us0sOEq_fck

KeyTrigger

KeyTrigger 用于在动画到达特定位置时触发事件函数。通过设置触发条件,响应动画进度的某一特定时刻执行特定操作。

  1. framePosition:触发事件的位置,以动画进度的百分比表示。范围是 0 到 100。
  2. motionTarget:指定触发事件的目标视图,通过视图 ID 进行关联。
  3. triggerReceiver:指定接收触发事件的视图,可以是另一个视图的 ID。
  4. onNegativeCross、onPositiveCross、onCross:这些属性指定了事件的回调函数,分别在以下条件下调用:
    • onNegativeCross:动画经过 framePosition 向负方向(回退)触发。
    • onPositiveCross:动画经过 framePosition 向正方向(前进)触发。
    • onCross:动画经过 framePosition 时(无论方向)触发。
  5. viewTransitionOnNegativeCross、viewTransitionOnPositiveCross、viewTransitionOnCross:这些属性指定了动画过度操作的 ID,对应 onNegativeCross、onPositiveCross 和 onCross 的触发条件。
  6. triggerSlack:触发的容差,表示触发事件前后的范围,用于避免重复触发。
  7. triggerId:触发事件的唯一 ID,用于在回调中识别触发事件。
  8. motion_postLayoutCollision:布尔值,设置为 true 时将在布局后检测碰撞触发。
  9. motion_triggerOnCollision:指定目标视图的 ID,用于检测 motionTarget 和目标视图的碰撞,发生碰撞时触发事件。

使用示例:

class TriggerText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {

    fun forwardText() {
        text = "我要前进"
    }

    fun retreatText() {
        text = "我要后退"
    }
}

自定义了TextView并声明了两个方法用以改变文本,XML布局文件如下:

<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/fragment_cl_img_filter_scene"
    app:showPaths="true">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/iv_filter_bottom"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginBottom="30dp"
        android:scaleType="centerCrop"
        android:src="@drawable/icon_flower"
        app:altSrc="@drawable/icon_cat_h"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <org.ninetripods.mq.study.widget.TriggerText
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/red"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="@+id/iv_filter_bottom"
        app:layout_constraintStart_toStartOf="@+id/iv_filter_bottom"
        app:layout_constraintTop_toBottomOf="@+id/iv_filter_bottom" />
</androidx.constraintlayout.motion.widget.MotionLayout>

MotionScene 动画核心代码:

<Transition
    motion:constraintSetEnd="@id/end"
    motion:constraintSetStart="@+id/start"
    motion:duration="1000"
    motion:motionInterpolator="linear">
    <KeyFrameSet>
        <!--在动画特定位置执行事件函数-->
        <KeyTrigger
            motion:framePosition="10"
            motion:motionTarget="@+id/tv_content"
            motion:onPositiveCross="forwardText"
            motion:viewTransitionOnCross="@color/red"
            motion:triggerSlack="10"/>
        <KeyTrigger
            motion:framePosition="90"
            motion:motionTarget="@+id/tv_content"
            motion:onNegativeCross="retreatText" />
    </KeyFrameSet>
    <OnSwipe
        motion:dragDirection="dragRight"
        motion:touchAnchorId="@+id/iv_filter_bottom"
        motion:touchAnchorSide="right" />
</Transition>

执行效果:

trigger
可以看到第一个 KeyTrigger 在动画进行到 10% 时(前进时)触发 forwardText 方法;第二个 KeyTrigger 在动画进行到 90% 时(后退时)触发 retreatText 方法。

KeyTrigger 可用于动态响应动画进度,或当视图达到某个位置时执行特定逻辑,比如播放音效、显示提示或修改视图属性等。相关视频:https://www.youtube.com/watch?v=Rqfufnlq_KU

示例

官方的一个简单综合示例:
示例

XML代码(layout_motion_moon):

<?xml version="1.0" encoding="utf-8"?>
<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"
    android:background="@drawable/background"
    app:layoutDescription="@xml/layout_moon_scene"
    app:showPaths="true">

    <ImageView
        android:id="@+id/moon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_moon" />

    <TextView
        android:id="@+id/credits"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Virginia P @ Google"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/swipe_hint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Swipe towards the right" />

</androidx.constraintlayout.motion.widget.MotionLayout>

对应的MotionScene代码:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="3000">

        <OnSwipe
            motion:dragDirection="dragRight"
            motion:touchAnchorId="@id/moon"
            motion:touchAnchorSide="bottom" />

        <KeyFrameSet>
            <KeyPosition
                motion:framePosition="25"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/moon"
                motion:percentY="0.6" />
            <KeyPosition
                motion:framePosition="50"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/moon"
                motion:percentY="0.5" />
            <KeyPosition
                motion:framePosition="75"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/moon"
                motion:percentY="0.6" />

            <KeyAttribute
                android:rotation="-360"
                android:scaleX="2.0"
                android:scaleY="2.0"
                motion:framePosition="50"
                motion:motionTarget="@id/moon" />

            <KeyAttribute
                android:rotation="-720"
                android:scaleX="1.0"
                android:scaleY="1.0"
                motion:framePosition="100"
                motion:motionTarget="@id/moon" />

            <KeyAttribute
                motion:framePosition="0"
                motion:motionTarget="@id/moon">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="#FFFFFF" />
            </KeyAttribute>

            <KeyAttribute
                motion:framePosition="50"
                motion:motionTarget="@id/moon">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="@color/yellow" />
            </KeyAttribute>
            <KeyAttribute
                motion:framePosition="100"
                motion:motionTarget="@id/moon">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="#FFFFFF" />
            </KeyAttribute>

            <KeyAttribute
                android:alpha="0.0"
                motion:framePosition="85"
                motion:motionTarget="@id/credits" />

            <KeyAttribute
                android:alpha="0"
                motion:framePosition="40"
                motion:motionTarget="@id/swipe_hint"
                motion:transitionEasing="accelerate" />
        </KeyFrameSet>

    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/moon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="100dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent" />

        <Constraint
            android:id="@id/credits"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:alpha="0.0"
            motion:layout_constraintBottom_toBottomOf="@id/moon"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="@id/moon" />
        <Constraint
            android:id="@id/swipe_hint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:visibility="visible"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/moon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="100dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent" />

        <Constraint
            android:id="@id/credits"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:alpha="1.0"
            motion:layout_constraintBottom_toBottomOf="@id/moon"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="@id/moon" />
        <Constraint
            android:id="@id/swipe_hint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="invisible"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
</MotionScene>

大致解释下上述代码的功能:

  • ConstraintSet 定义了 start 和 end 状态,描述了动画开始和结束时视图的位置与属性。moon 视图从父布局左下移动到右下,同时改变大小、旋转、颜色等;
  • 定义了 OnSwipe 手势控制,允许用户通过向右滑动 moon 的底部触发动画。
  • 使用 KeyFrameSet 描述了动画过程中多个关键帧的效果,包括:

位置控制 (KeyPosition):通过 Y 轴的百分比,创建了平滑的移动路径。
属性变化 (KeyAttribute):定义旋转角度、缩放比例、透明度等属性。
自定义属性 (CustomAttribute):改变视图的颜色滤镜。

资料

1、视频介绍:https://www.youtube.com/results?search_query=Motion+Tags
2、CodeLabs:https://codelabs.developers.google.com/codelabs/motion-layout?hl=zh-cn#0

### 如何在 Android 中使用 MotionLayout 创建动画 #### 定义布局文件中的 `MotionLayout` `MotionLayout` 是一种特殊的布局管理器,继承自 `ConstraintLayout`。这意味着可以在定义视图位置和大小的同时指定这些属性随时间变化的方式。 ```xml <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/motion_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layoutDescription="@xml/scene_file"> <!-- 子View --> </androidx.constraintlayout.motion.widget.MotionLayout> ``` 上述代码展示了如何声明一个 `MotionLayout` 控件并关联到场景描述文件[^1]。 #### 场景描述文件 (`@xml/scene_file`) 的结构 为了使 `MotionLayout` 能够工作,需要创建 XML 文件来描述不同状态下的约束条件以及两者之间的转换方式: ```xml <MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition motion:constraintSetStart="@+id/start" motion:constraintSetEnd="@+id/end"/> <ConstraintSet android:id="@+id/start"> <!-- 初始状态下各组件的位置和其他属性设置 --> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <!-- 结束状态下各组件的新位置和其他属性设置 --> </ConstraintSet> </MotionScene> ``` 此配置指定了两个不同的约束集——起始(`start`) 和结束(`end`) ,并通过 `<Transition>` 标签连接起来形成完整的运动路径[^2]。 #### 动态控制动画播放 除了静态设定外,还可以编程地触发或调整动画过程。例如,在 Java/Kotlin 代码里调用相应的方法启动特定的过渡动作或者改变当前进度值: ```java // 获取 MotionLayout 实例 MotionLayout motionLayout = findViewById(R.id.motion_layout); // 开始向目标状态转变 motionLayout.transitionToEnd(); // 或者手动设置中间某个百分比的状态 float progressValue = 0.5f; // 表示一半完成度 motionLayout.setProgress(progressValue); ``` 以上方法允许开发者灵活操控由 `MotionLayout` 所驱动的各种交互行为[^4]。 #### 添加关键帧 (Optional) 如果希望更精细地定制动画流程,则可以通过添加额外的关键帧节点进一步增强表现力。这使得某些时刻内的视觉特性得以精确调控而不必依赖线性的插值算法。 ```xml <KeyFrameSet> <KeyAttribute app:framePosition="50" app:motionTarget="@id/my_view"> <CustomAttribute app:attributeName="alpha" app:value="0.5"/> </KeyAttribute> </KeyFrameSet> ``` 这里展示了一个简单的例子,其中当达到整个动画周期的一半时会将某对象透明度设为半透明白色[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小马快跑_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值