1、概述
I / O '18提到了MotionLayout,当时还没有正式发布前段时间,在今年的6月26日正式发布了ConstraintLayout的2.0alpha版,也算正式推出了MotionLayout。 MotionLayout是ConstraintLayout的子类,它具有ConstraintLayout的所有属性。MotionLayout用来处理两个ConstraintSet之间的切换,并在根据两个ConstraintSet的CustomAttribute参数来自动生成切换动画,关于ConstraintSet下面会讨论。同时MotionLayout所增加的是可以直接通过触摸屏幕来控制动画的运行进度。也就是说MotionLayout会管理你的触摸事件通过跟踪手指的速度,并将其与系统中的视图速度相匹配。从而可以自然地在两者之间通过触摸滑动平稳过渡。并且在动画里面加入了关键帧的概念,使得其自动生成动画在运行时某一阶段会运行到关键帧的状态。同时MotionLayout支持在XML中完全描述一个复杂的动画,而不需要通过Java代码来实现。
2、ConstraintLayout动画
ConstraintLayout中的动画要借助于ConstraintSet。ConstraintSet是一个轻量级对象,表示ConstraintLayout中所有子元素的constraints,margins和padding 。当将 ConstraintSet应用于显示ConstraintLayout时,布局会使用ConstraintSet中的约束来更新对应ConstraintLayout中的。ConstraintSet仅为视图的大小和位置设置动画,不会为其他属性设置动画(例如颜色)。
下面的代码示例显示了如何动画将单个按钮移动到屏幕底部:
public class MainActivity extends AppCompatActivity {
ConstraintLayout constraintLayout;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.keyframe_one);
constraintLayout = findViewById(R.id.constraint_layout);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
animateToKeyframeTwo();
}
});
}
void animateToKeyframeTwo() {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.load(this, R.layout.keyframe_two); //载入要更新的布局到constraintSet中
TransitionManager.beginDelayedTransition(constraintLayout); // 开启
constraintSet.applyTo(constraintLayout);
}
}
这边要注意一下,只会运行R.layout.keyframe_two中与R.layout.keyframe_one中id对应的动画。不会出现R.layout.keyframe_two中有而R.layout.keyframe_one中没有的视图,也不会对R.layout.keyframe_one有但R.layout.keyframe_two中没有的视图有任何效果。
3、MotionLayout动画
前面已经演示了怎么对ConstraintLayout布局设置动画,现在来讨论下MotionLayout布局下的动画。
3.1 MotionScene
与通常的布局不同,MotionLayout所做的约束保存在一个单独的XML文件MotionScene中,该文件存储在您的res/xml目录中。
MotionScene文件可以包含指定动画所需的全部内容,例如前面提到的ConstraintSets、ConstraintSets直接的过渡、关键帧、触摸处理等等。
3.2 创建动画简单流程
3.2.1 先决条件
① Android Studio 3.2.0或更高版本
② 运行Android API等级21或更高版本的设备或模拟器
③ 添加依赖:
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'
或者
implementation 'androidx.appcompat:appcompat:1.0.0-beta1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha1'
这边要说明一下androidx也是今年IO刚刚推出的一个依赖,用来替代之前的com.android.support依赖。添加了它就不用添加一大堆v4、v7、v13等依赖了。
3.2.2 定义布局
前面提到过MotionLayout是ConstraintLayout的子类,所以MotionLayout可以直接替换ConstraintLayout。因为ConstraintLayout有的功能MotionLayout都有。
3.2.3 创建MotionScene
这一步是MotionLayout的关键,在res下的xml文件夹中创建MotionScene。其实在MotionLayout中可以不用添加想进行动画的视图的约束,而将约束放在ConstraintSet中,在将ConstraintSet放在MotionScene中。
这边需要注意的是每个ConstraintSet 里面的元素必须始终指定所需的位置和所需的大小。它会覆盖任何以前设置的布局信息。并且里面的id和MotionLayout中的视图的id要对应才会有反应。
为了帮助MotionLayout 的视图理解必须约束集的顺序,需要创建一个Transition 元素。通过使用其直观命名 constraintSetStart 和constraintSetEnd 属性,可以指定首先应用哪个集合以及最后应用哪个集合。该Transition 元素还允许指定动画的持续时间。
// 放在上面的 和 之中和ConstraintSet 标签平级。
此时,一个简单的MotionScene完成。但是此时任然没有和MotionLayout进行绑定。需要给MotionLayout添加app:layoutDescription属性来将上面的MotionScene绑定:
app:layoutDescription="@xml/my_scene"
3.2.4 启动动画
运行应用程序时,MotionLayout 视图将自动将constraintSetStart 属性中指定的约束集设置到自己身上。因此,要启动动画,需要做的就是调用transitionToEnd() 方法从而实现ConstraintSet之间的转换:
motion_container.transitionToEnd();
3.2.5 动画执行进度监听
可以通过给MotionLayout 设置监听器来监听动画进度,和动画完成时的回调:
motionLayout.setTransitionListener(new MotionLayout.TransitionListener() {
@Override
public void onTransitionChange(MotionLayout motionLayout, int i, int i1, float v) {
seekBar.setProgress((int)(v*100));
}
@Override
public void onTransitionCompleted(MotionLayout motionLayout, int i) {
}
});
上面对进度的监听通过seekbar表示出来
3.3 关键帧(Key Frames)
在上面的动画中,Button小部件看起来像在直线的路径中移动。这是因为MotionLayout 的视图此时其实只有两个关键帧:起始帧Button位于屏幕的右下角,终点帧Button位于屏幕的左上角。如果要改变路径的形状,则必须提供一些介于起点和终点之间关键。
在开始创建关键帧之前,必须将KeyFrameSet 标签添加到MotionScene之中。可以自由创建任意数量的关键帧。
...
MotionLayout 视图支持许多不同类型的关键帧。这里使用其中两种类型:KeyPosition 和KeyCycle 。
3.3.1 KeyPosition
KeyPosition 可以帮助视图改变运动路径的形状。创建它们时,请确保提供目标视图的ID,沿时间轴的位置,可以是0到100之间的任意数字,以及指定X或Y坐标已经运行到的百分比。可以设置type参数指出坐标是相对于实际的X或Y轴,还是相对于路径本身。
上面第一个KeyPosition代表button按钮在运行道30%的时候,相对于运行轨迹x已经运行了85%了。第二个KeyPosition代表button按钮在运行道60%的时候,相对于运行轨迹x已经运行了100%了.效果如下,这样就可以避开和seekbar的冲突了:
3.3.2 KeyCycle
KeyCycle用来给动画添加振动。可以通过提供诸如要使用的波形和波形周期等详细信息来配置KeyCycle。下面是KeyCycle支持的各种振动波形:
在上述动画中加入如下KeyCycle
3.4 交互式动画
上面的动画运行我都是通过对Button按钮设置点击监听事件,然后调用motion_container.transitionToEnd();方法来使他运行的。其实完全不必这么麻烦,因为MotionLayout的视图允许开发者将触摸事件直接附加到视图中。截止到现在,它支持点击和滑动事件。要实现上面实现的点击事件可以在MotionScene中增加代码如下:
而可以通过给MotionScene增加OnSwipe标签来使视图通过在屏幕滑动而大运行。在创建该标签时,必须确保提供正确的拖动方向以及应作为拖动控制柄的视图的边。可以这么理解,相对于初始位置,如果想往上滑起到增加动画进度就设置为dragUp,想往下滑起到增加动画进度就设置为dragDown,左右同样道理。至于touchAnchorSide这个参数的本意应该设置拉目标视图的边,但我发现就算不设置touchAnchorSide这个参数或者设置成任意值top bottom或者left right,对动画都没有影响。这可能是MotionLayout的一个bug毕竟现在还只是alpha版。
5、MotionEditor
之前一篇讨论ConstraintLayout的文章,基本上都是在布局编辑器中进行操作。这也是ConstraintLayout的一大优点,MotionLayout作为其子类,官方也为它专门提供了强大的可视化编辑器。不过可惜的是,到目前为止还不能使用,下面是MotionEditor的官方预告片的一个节选: