两个橘子
读完需要17分钟
速读仅需 8 分钟
链接:https://juejin.cn/post/7361392237887242276
先附上一个简单的动画效果图:
MotionLayout是ConstraintLayout的子类,具有ConstraintLayout的所有功能。
dependencies {
implementation(libs.constraintlayout)
}
[versions]
constraintlayout = "2.1.4"
[libraries]
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
ConstraintLayout 的版本需要更新到2.0以上。
<?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.constraintlayout.widget.ConstraintLayout>
然后将布局转换为MotionLayout,如下图。
转换之后布局如下:
<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="wrap_content"
android:layout_height="match_parent"
app:layoutDescription="@xml/view_nav_scene">
......
</androidx.constraintlayout.motion.widget.MotionLayout>
根布局会自动转换为MotionLayout并且添加了一个属性app:layoutDescription,这个属性所引用的文件就是我们要编写的动画描述文件。
转换之后在Design面板会多出一个预览窗口。
<?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="1000">
<KeyFrameSet>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
</ConstraintSet>
</MotionScene>
这也是一个MotionScene文件的基本结构。
如果我们不使用AndroidStudio来转换布局为MotionLayout的话,就需要自己在res\xml文件夹下创建一个根节点为MotionScene的xml文件。
4. 预览 预览画面如下图所示:
点击箭头1所指的start可以预览动画start状态。 点击箭头2所指的end可以预览动画end状态。 点击箭头3所指的start和end之间的连线可以在下方的面板中点击播放查看动画。
如果我们只设置了motion:showPaths="true"的话那么就会显示轨迹线,如果同时设置了motion:showPaths和motion:motionDebug的话,showPaths的设置会失效。以motionDebug的设置为准。
Transition标签定义的可处理事件有三种:OnClick、OnSwipe、KeyFrameSet。
用于处理用户点击事件。
用户处理用户拖拽事件。
用来描述一系列运动过程中的关键帧。可以利用它使动画效果变的更复杂。其子元素包含KeyPosition、KeyAttribute、KeyCycle、KeyTimeCycle、KeyTrigger。
指定动画序列中特定时刻的位置(中间状态的位置),用于调整默认的运动路径。
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentX="0.5"
motion:percentY="0.2" />
</KeyFrameSet>
motion:percentX="0.5",motion:percentY="0.2"就是下图中(0.5,0.2)的位置。
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="deltaRelative"
motion:percentX="0.5"
motion:percentY="0.2" />
</KeyFrameSet>
motion:percentX="0.5",motion:percentY="0.2"就是下图中(0.5,0.2)的位置。
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentX="0.5"
motion:percentY="0.2" />
</KeyFrameSet>
motion:percentX="0.5",motion:percentY="0.2"就是下图中(0.5,0.2)的位置。
以下代码代表动画开始时View的宽高为 200px() (percentWidth = 0, percentHeight = 0),动画结束时的宽高为 600px(percentWidth = 1, percentHeight = 1),当framePosition = 50 时,view位于坐标系中(0.5,0.2)的位置,宽度变为 0.8,高度变为0.8,相当于此时view的宽高为 600px x 0.8。
<?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">
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:keyPositionType="pathRelative"
motion:motionTarget="@id/view_test"
motion:percentX="0.5"
motion:percentY="0.2"
motion:percentWidth="0.8"
motion:percentHeight="0.8"/>
</KeyFrameSet>
<OnClick
motion:clickAction="toggle"
motion:targetId="@id/view_test" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/view_test"
android:layout_width="200px"
android:layout_height="200px"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/view_test"
android:layout_width="600px"
android:layout_height="600px"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
</MotionScene>
指定动画序列中特定时刻的视图属性。
如设置动画到一半时透明度为0.2,缩放为原控件的0.2倍:
<KeyFrameSet>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/view_test"
android:alpha="0.2"
android:scaleX="0.2"
android:scaleY="0.2" />
</KeyFrameSet>
根据给定的函数对设定的属性进行周期性变化。
KeyTimeCycle 可以在关键帧上按照一些周期函数,周期性的改变其属性值。 KeyTimeCycle 是在帧上做周期性,KeyCycle 是在动画过程中做周期性。
在动画中调用控件的指定方法。
public class CustomButton extends AppCompatToggleButton {
......
/**
* 添加的方法
*/
public void toggle() {
setChecked(!isChecked());
}
}
MotionScene :
<KeyFrameSet>
<KeyTrigger
motion:framePosition="50"
motion:motionTarget="@id/view_btn"
motion:onCross="toggle" />
</KeyFrameSet>
效果图: 开关由关闭状态变为开启状态
用来设置视图在开始或者结束时各个控件的位置和大小等状态。只有一个id标签,可以有多个Constraint元素。
每一个Constraint元素对应一个id属性所指向的View。 Constraint元素中我们可以设置控件的大小并使用ConstraintLayout的属性来设置控件位置。还可以插入以下属性:
包含在Constraint元素中,一个 本身包含两个属性:
指定自定义属性时,必须在开始和结束的 ConstraintSet 元素中都为其指定。如下代码所示:
<ConstraintSet android:id="@+id/start">
<Constraint
......
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="@color/viewColor" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
......
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="@color/transformColor" />
</Constraint>
</ConstraintSet>
效果图如下:
首先在布局文件中添加一个ImageView来显示图片,添加一个等大小的View作为背景。
<?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"
app:layoutDescription="@xml/view_fullscreen_scene">
<View
android:id="@+id/view_top"
android:layout_width="match_parent"
android:layout_height="360px"
android:background="@color/black" />
<ImageView
android:id="@+id/iv_top"
android:layout_width="0px"
android:layout_height="0px"
android:scaleType="centerCrop"
android:src="@mipmap/img" />
</androidx.constraintlayout.motion.widget.MotionLayout>
接下来为图片ImageView和背景View在MotionScene中添加动画,首先设置图片和背景的开始状态。
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/view_top"
android:layout_width="match_parent"
android:layout_height="360px"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/iv_top"
android:layout_width="match_parent"
android:layout_height="0px"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toEndOf="@id/view_top"
motion:layout_constraintStart_toStartOf="@id/view_top"
motion:layout_constraintTop_toTopOf="@+id/view_top" />
</ConstraintSet>
然后设置图片和背景的结束状态。
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/view_top"
android:layout_width="match_parent"
android:layout_height="120px"
motion:layout_constraintBottom_toTopOf="@+id/nav"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@+id/iv_top"
android:layout_width="0px"
android:layout_height="0px"
android:layout_marginTop="10px"
android:layout_marginBottom="10px"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintDimensionRatio="W, 4:3"
motion:layout_constraintStart_toStartOf="@id/view_top"
motion:layout_constraintTop_toTopOf="@+id/view_top" />
</ConstraintSet>
ConstraintSet标签用来设置界面一种状态下的布局(ConstraintLayout) Constraint标签用来描述控件的位置和属性等。 这里设置ImageView和View的开始是宽度为match_parent,高度为360px,结束时宽度不变,高度为120px。并设置ImageView结束时的尺寸比为 4 :3。
需要在开始和结束的两个Constraint中为控件设置大小,即使控件大小没有改变也需要在两边都设置好大小。
然后设置开始和结束状态,动画执行时间。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
</Transition>
然后对上方View添加手势响应动作。在Transition标签中添加OnSwipe标签即可。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/view_top"
motion:touchAnchorSide="bottom" />
</Transition>
OnSwipe表示拖动执行动画。motion:dragDirection="dragDown"表示向下边拖动执行动画。
完善ImageView的动画
动画执行到85的进度时,保持宽度及x位置不变。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/view_top"
motion:touchAnchorSide="bottom" />
<KeyFrameSet>
<KeyPosition
motion:framePosition="85"
motion:motionTarget="@id/iv_top"
motion:percentWidth="0"
motion:percentX="0" />
</KeyFrameSet>
</Transition>
效果图
按钮动画较为简单,平移+渐变。
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="100px"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toStartOf="@id/iv_pause"
motion:layout_constraintTop_toTopOf="@id/view_top" />
<Constraint
android:id="@+id/iv_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="100px"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toStartOf="@id/iv_next"
motion:layout_constraintTop_toTopOf="@id/view_top" />
<Constraint
android:id="@+id/iv_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="50px"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toEndOf="@id/view_top"
motion:layout_constraintTop_toTopOf="@id/view_top" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/iv_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="100px"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toStartOf="@id/iv_pause"
motion:layout_constraintTop_toTopOf="@id/view_top" />
<Constraint
android:id="@+id/iv_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="100px"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toStartOf="@id/iv_next"
motion:layout_constraintTop_toTopOf="@id/view_top" />
<Constraint
android:id="@+id/iv_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="50px"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintEnd_toEndOf="@id/view_top"
motion:layout_constraintTop_toTopOf="@id/view_top" />
</ConstraintSet>
动画执行到85的进度时,透明度变为0.1。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/view_top"
motion:touchAnchorSide="bottom" />
<KeyFrameSet>
<KeyAttribute
android:alpha="0.10"
motion:framePosition="85"
motion:motionTarget="@id/iv_previous" />
<KeyAttribute
android:alpha="0.10"
motion:framePosition="85"
motion:motionTarget="@id/iv_pause" />
<KeyAttribute
android:alpha="0.10"
motion:framePosition="85"
motion:motionTarget="@id/iv_next" />
</KeyFrameSet>
</Transition>
效果图:
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="@id/view_top"
motion:layout_constraintStart_toEndOf="@id/view_top"
motion:layout_constraintTop_toTopOf="@id/view_top" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30px"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@+id/view_top"
motion:layout_constraintStart_toEndOf="@+id/iv_top"
motion:layout_constraintTop_toTopOf="@+id/view_top" />
</ConstraintSet>
动画执行到85的进度时,透明度为0.1。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/view_top"
motion:touchAnchorSide="bottom" />
<KeyFrameSet>
<KeyAttribute
android:alpha="0.10"
motion:framePosition="85"
motion:motionTarget="@id/tv_text" />
</KeyFrameSet>
</Transition>
效果图:
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/iv_round"
android:layout_width="0px"
android:layout_height="0px"
android:alpha="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/iv_round"
android:layout_width="450px"
android:layout_height="450px"
android:alpha="1"
motion:layout_constraintBottom_toTopOf="@id/view_top"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
动画执行到50的进度时,透明度为0。
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/view_top"
motion:touchAnchorSide="bottom" />
<KeyFrameSet>
<KeyAttribute
android:alpha="0"
motion:framePosition="50"
motion:motionTarget="@id/iv_round" />
</KeyFrameSet>
</Transition>
屏幕外-->屏幕底部,就不放图了。
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
motion:layout_constraintTop_toBottomOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
代码整合之后文章开头的动画效果就实现了。
到这里一些基本动画效果及属性就介绍完了。
代码地址:https://github.com/shimoat/MyMotionLayout