MotionLayout系列之三

1,266 阅读4分钟

在这里先简单的介绍一下keyFrameSet的使用,最终实现效果如下

这里可以看到起点为左下角,终点为右下角,但是在中途向上拐了一下,这就用到了keyFrameSet

<KeyFrameSet>
    <KeyPosition
            app:framePosition="50"
            app:motionTarget="@id/moon"
            app:keyPositionType="parentRelative"
            app:percentY="0.5"
    />
</KeyFrameSet>

这里的KeyFrameSet是transition的子元素,它是所有关键帧的集合,它包含了KeyPosition,KeyAttribute,KeyCycle,KeyTimeCycle。这里用到的是KeyPosition,它的含义是在framePosition通过percentX或者percentY修改motionTarget的路径,而这里的percentX或者percentY又是根据KeyPositionType来定的。

  • framePosition:它的值是0-100,表示动画完成百分之几时的位置
  • motionTarget:这个framePosition修改路径的view,也就是修改哪个移动的view的路径
  • KeyPositionType:这个framePosition是如何修改这个路径的,它有三个值分别为parentRelative、pathRelative、deltaRelative,至于这三个值的区别接下来会介绍
  • percentY|percentX:到framePosition时路径修改为多少,这个值是0.0-1.0,当然负数和大于1的值也可以

实现上述动画的xml为

<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
             xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- A transition describes an animation via start and end state -->
    <Transition
            
            app:constraintSetStart="@+id/start"
            app:constraintSetEnd="@+id/end"
            app:duration="3000">
        <OnClick
                app:targetId="@id/moon"
                app:clickAction="toggle"
        />

        <KeyFrameSet>
            <KeyPosition
                    app:framePosition="50"
                    app:motionTarget="@id/moon"
                    app:keyPositionType="parentRelative"
                    app:percentY="0.5"
            />
        </KeyFrameSet>
    </Transition>

    <!-- Constraints to apply at the start of the animation -->
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@id/moon"
            
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
        />
        <Constraint
            android:id="@id/credits"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:alpha="0.0"
            android:layout_marginStart="16dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/moon"
            app:layout_constraintTop_toTopOf="@id/moon"
            />
    </ConstraintSet>

    <!-- Constraints to apply at the end of the animation -->
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@id/moon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
        />
        <Constraint
                android:id="@id/credits"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:alpha="1.0"
                android:layout_marginStart="16dp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintBottom_toBottomOf="@id/moon"
                app:layout_constraintTop_toTopOf="@id/moon"
        />
    </ConstraintSet>
</MotionScene>

注意要是想显示动画路径需要在activity的xml中加入motionDebug=“SHOW_PATH”,所以activity中的代码为

<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:motionDebug="SHOW_PATH"
        app:layoutDescription="@xml/step3_completed" >


    <ImageView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:srcCompat="@drawable/background"
            android:scaleType="centerCrop"
    />

    <ImageView
            android:id="@+id/moon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/ic_moon"
            android:tint="?colorOnPrimary"
    />

    <TextView
            android:id="@+id/credits"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/credits"
            android:textSize="24sp"
            android:textColor="?colorOnPrimary" />
</androidx.constraintlayout.motion.widget.MotionLayout>

parentRelative它的坐标系就是屏幕的坐标系

通常情况下,你用它来做与父容器相关的大范围运动。 deltaRelative它的坐标系是起始位置为(0,0),结束位置为(1,1)

PathRelative它的坐标系为起始位置为(0,0),结束位置为(1,0)

插入多个关键帧,比如实现下面的path

设置的关键帧为

<KeyFrameSet>
    <KeyPosition
            app:framePosition="25"
            app:motionTarget="@id/moon"
            app:keyPositionType="parentRelative"
            app:percentY="0.6"
    />

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

如果实现下面的path

关键帧代码为

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

KeyFrameSet之KeyCycle

这个是给动画添加运动波形的,比如正余弦,它的属性主要有以下几种

  • motionTarget: 执行动画的view的id
  • framePosition:   动画差值的位置0-100
  • Standard View attributes: 透明、旋转、缩放、平移等属性(translationX)
  • waveShape:产生的波形图{sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce}
  • wavePeriod: 波形在固定位置附近的循环周期
  • waveOffset offset
  • transitionPathRotate: 相对于view的移动路径的旋转
  • progress: 调用view的setProgress方法 (used to talk to nested ConstraintLayouts etc.)
  • CustomAttribute 通过反射调用view的setXXX方法 (仅限于值为float)
<KeyFrameSet>
    <KeyCycle
        android:translationY="50dp"
        motion:framePosition="100"
        motion:motionTarget="@id/button"
        motion:waveOffset="0"
        motion:wavePeriod="0"
        motion:waveShape="bounce" />
    <KeyCycle
        android:translationY="50dp"
        motion:framePosition="50"
        motion:motionTarget="@id/button"
        motion:waveOffset="0"
        motion:wavePeriod="1"
        motion:waveShape="bounce" />
    <KeyCycle
        android:translationY="50dp"
        motion:framePosition="0"
        motion:motionTarget="@id/button"
        motion:waveOffset="0"
        motion:wavePeriod="0"
        motion:waveShape="bounce" />
</KeyFrameSet>

比如这里选用的是bounce,运动轨迹如下

将每一个keyCycle的waveShape改为square,效果如下

将每一个keyCycle的waveShape改为cos,效果如下

将每一个keyCycle的waveShape改为reverseSawtooth,效果如下
将每一个keyCycle的waveShape改为sawtooth,效果如下

将每一个keyCycle的waveShape改为sin,效果如下

将每一个keyCycle的waveShape改为triangle,效果如下