Android开发MotionLayout:打开动画新世界大门进阶教程 - 掘金开发者动态
做Android开发的朋友,应该都听过MotionLayout这个东西。它刚出来的时候,很多人说它是 CoordinatorLayout 和 PropertyAnimationHolder 的结合体,能搞定各种复杂的手势和动画,但是真上手的时候,又会觉得文档讲得太浅,基础入门之后就不知道怎么进阶玩点真东西了。
我最开始接触MotionLayout的时候,也踩了不少坑。最早我只是用它做一些简单的转场动画,点击按钮弹出个侧边栏这种,觉得好像也就那样,还不如我自己写属性动画方便。直到去年做一个首页上下滑动切换头部折叠效果的需求,试了各种方案都不对,要么手势冲突,要么卡顿掉帧,最后抱着试试的心态用MotionLayout改,不到半天就搞定了,那时候才发现,这东西真的打开了动画开发的新世界大门。
今天就不说那些入门的基础配置了,网上一搜一大堆,咱们直接讲进阶能用得上的干货。
第一个进阶技巧,就是自定义Progress的控制,很多人不知道,MotionLayout默认是跟手势走,根据滑动进度自动更新关键帧位置,但其实我们完全可以自己手动控制这个进度,适配更复杂的业务逻辑。
举个例子,我之前做需求的时候,需要跟NestedScrolling嵌套滑动联动,也就是用户滑动列表的时候,列表滚动多少,MotionLayout的动画就走百分之多少。这个时候直接给MotionLayout setProgress就可以了?不对,直接这么写,会跟MotionLayout自带的触摸处理冲突,划动的时候会抖。
正确的做法是,先在xml里把MotionScene的app:motionProgress默认设出来,然后如果需要自己控制,就把 MotionLayout 的 setOnTouchListener 给拦截掉,或者直接禁用掉自带的触摸控制:app:motionTouchEnable="false",然后在你自己的滑动回调里,比如NestedScrolling的onScrolled方法里,根据你算出来的偏移量,设置0到1之间的progress就好了。
我之前做头部折叠就是这么写的,列表往上滑,头部慢慢从展开变成折叠,透明度从1变到0,整个过程丝滑,根本不需要自己写动画更新,所有的关键帧状态MotionLayout帮你算好了,你只需要给进度就行。
第二个进阶玩法,就是关键帧的自定义插值器。很多人写MotionScene的时候,都是直接写两个ConstraintSet,一个起始一个结束,默认就是匀速过渡。但实际产品需求里,哪有那么多多匀速动画?比如我们想要一个先快后慢的回弹效果,或者中间放大再缩小的弹性效果,默认配置就搞不定了。
这个时候,你可以给每个属性单独设置插值器。比如我要让一个图标在动画过程中,先放大到1.2倍再缩回到1倍,不用写三个ConstraintSet,直接在起始或者结束的Constraint里,给motion:attribute设置motion:transitionInterpolator就行了。
举个具体的写法,就是在标签里加:
这样这个属性的变化就会走你指定的插值器,跟其他属性互不干扰。你甚至可以给不同的属性用不同的插值器,比如位移用加速,透明度用减速,做出来的动画效果比统一插值自然太多了。
还有很多人不知道,你甚至可以自定义Interpolator,直接写你的自定义类的全路径就能用,完全支持复杂的弹性插值,不用自己写代码动态改。
第三个很多人踩坑的点,就是MotionLayout和RecyclerView的嵌套冲突。这应该是进阶开发里最常见的问题了,你把RecyclerView放在MotionLayout里面,想要做滑动触发动画,要么RecyclerView滑不动,要么MotionLayout没反应,两大滑动控件打架,谁都不让谁。
解决这个问题其实很简单,MotionLayout本身提供了requestDisallowInterceptTouchEvent的处理,你只需要开一个配置就行了。在xml的MotionLayout标签里加上app:motionLayout_requiresNoNestedScrollingChild="false"不对,应该是app:nestedScrollingEnabled="true",然后给你的RecyclerView设置OnScrollChangeListener,在滚动的时候把你计算好的progress给MotionLayout,就像我刚才说的手动控制进度的方法。
如果还是有冲突,你可以自定义一个继承MotionLayout的子类,重写onInterceptTouchEvent方法,根据触摸位置和滑动方向自己判断要不要拦截事件。比如用户往下拉的时候,已经到列表顶部了,这个时候就让MotionLayout拦截,做下拉伸展的动画,往上滑的时候就把事件给RecyclerView,让列表滚动。
我之前做下拉刷新转场的需求就是这么处理的,完全没冲突,跟系统自带的滑动一样流畅。
第四个进阶玩法,就是用MotionLayout做共享元素转场。现在很多App的列表点击跳转详情,都要有个共享元素动画,从列表的小图变成详情页的大图,传统的SharedElementTransition有时候会跟Activity转场动画冲突,或者在某些机型上有卡顿掉帧的问题,用MotionLayout做反而更简单,效果也更稳。
具体怎么做呢?你只需要在起始Activity和目标Activity各放一个MotionLayout,起始页的结束关键帧就是目标页共享元素的位置,然后在startActivity的时候,把当前的progress传给目标页,目标页onCreate的时候,根据传过来的progress慢慢动画到结束状态就行。
比系统自带的共享元素好的地方是,你可以控制动画的整个过程,哪怕用户中途按返回,你也可以反向做动画退回去,不会出现闪屏的问题,而且支持同时给多个元素做动画,不光是位置大小,颜色透明度都能一起动,做出来的转场效果要细腻很多。
最后说一个很多人忽略的点,就是MotionLayout的性能问题。很多人说MotionLayout卡,其实是你用错了。MotionLayout本质上还是在绘制的时候更新属性,如果你一个布局里放了十几个动画同时动,肯定会掉帧,但是只要你做好两件事,就能保证流畅。
第一,不需要动的控件,把它放到ConstraintSet外面,或者标记为motion:constraintBehavior="unchanged",这样MotionLayout不会每次更新都去重新计算它的属性,节省很多性能。第二,如果你做的是跟手势联动的动画,打开app:motionStickyTransitions="true",它会自动优化掉中间不必要的刷新帧,滑动的时候更跟手。
其实MotionLayout出来这么多年了,早就不是那个实验性的东西了,现在Android开发的复杂动画需求,用它真的能省很多事,不用你写一堆属性动画,也不用处理各种手势冲突,大部分工作它都帮你做好了,你只需要关心关键帧的状态就行。
我现在做任何带交互动画的需求,第一反应都是用MotionLayout写,原来两三天才能搞定的复杂效果,现在大半天就能出来,而且稳定性比自己写的好太多。如果你们还停留在只会用它做简单转场的阶段,不妨试试今天说的这几个进阶技巧,说不定就能打开你自己的动画开发新世界。
Android开发,MotionLayout,Android动画,MotionLayout进阶,Android MotionLayout,安卓开发,动画开发,MotionScene,Android布局,嵌套滑动
[Q]:MotionLayout如何手动控制动画进度?
[A]:先禁用MotionLayout自带的触摸控制,设置`app:motionTouchEnable="false"`,之后就可以在自己的滑动、事件回调中,根据计算得到的0-1区间数值,调用`setProgress`来控制动画推进进度,适配自定义联动需求。
[Q]:MotionLayout能给不同属性设置不同的插值器吗?
[A]:可以,只需要在对应Constraint标签内,给单独属性添加``标签,指定属性名和想要使用的插值器即可,不同属性的插值器互不干扰,还支持使用自定义插值器。
[Q]:MotionLayout和RecyclerView滑动冲突怎么解决?
[A]:首先开启MotionLayout的`app:nestedScrollingEnabled="true`,采用手动控制进度的方式,在RecyclerView的滚动回调中给MotionLayout传递进度;如果仍有冲突,可以自定义MotionLayout子类,重写onInterceptTouchEvent,根据滑动方向和位置判断拦截逻辑即可。
[Q]:MotionLayout适合做共享元素转场吗?
[A]:非常适合,相比系统自带的共享元素转场,MotionLayout可以完整控制动画全过程,支持多属性同时过渡,中途返回也能流畅反向动画,不容易出现闪屏、卡顿问题,效果更稳定细腻。
[Q]:MotionLayout为什么会出现卡顿掉帧?
[A]:大多是使用方式不对导致的,不需要参与动画变化的控件,要标记为`motion:constraintBehavior="unchanged"`避免重复计算;跟手势联动的动画可以开启`motionStickyTransitions`优化刷新,就能有效提升流畅度。
[Q]:MotionLayout只能做预设好的转场动画吗?
[A]:不是,MotionLayout支持和自定义手势、嵌套滑动、滚动联动,只要手动更新进度就可以适配任意的自定义交互,不局限于预设的固定转场。
[Q]:入门MotionLayout之后,进阶需要先掌握哪些内容?
[A]:首先掌握手动控制进度、自定义插值器这两个基础进阶能力,然后解决最常见的滑动冲突问题,再进一步尝试共享元素转场、性能优化这些高阶玩法,就能应对大多数复杂动画需求。
[Q]:MotionLayout比自己写属性动画好在哪里?
[A]:MotionLayout把关键帧状态、插值器、手势冲突处理都封装好了,开发者只需要定义起始和结束状态,不需要编写大量的属性动画更新和事件处理代码,开发效率更高,稳定性也更好。
我最开始接触MotionLayout的时候,也踩了不少坑。最早我只是用它做一些简单的转场动画,点击按钮弹出个侧边栏这种,觉得好像也就那样,还不如我自己写属性动画方便。直到去年做一个首页上下滑动切换头部折叠效果的需求,试了各种方案都不对,要么手势冲突,要么卡顿掉帧,最后抱着试试的心态用MotionLayout改,不到半天就搞定了,那时候才发现,这东西真的打开了动画开发的新世界大门。
今天就不说那些入门的基础配置了,网上一搜一大堆,咱们直接讲进阶能用得上的干货。
第一个进阶技巧,就是自定义Progress的控制,很多人不知道,MotionLayout默认是跟手势走,根据滑动进度自动更新关键帧位置,但其实我们完全可以自己手动控制这个进度,适配更复杂的业务逻辑。
举个例子,我之前做需求的时候,需要跟NestedScrolling嵌套滑动联动,也就是用户滑动列表的时候,列表滚动多少,MotionLayout的动画就走百分之多少。这个时候直接给MotionLayout setProgress就可以了?不对,直接这么写,会跟MotionLayout自带的触摸处理冲突,划动的时候会抖。
正确的做法是,先在xml里把MotionScene的app:motionProgress默认设出来,然后如果需要自己控制,就把 MotionLayout 的 setOnTouchListener 给拦截掉,或者直接禁用掉自带的触摸控制:app:motionTouchEnable="false",然后在你自己的滑动回调里,比如NestedScrolling的onScrolled方法里,根据你算出来的偏移量,设置0到1之间的progress就好了。
我之前做头部折叠就是这么写的,列表往上滑,头部慢慢从展开变成折叠,透明度从1变到0,整个过程丝滑,根本不需要自己写动画更新,所有的关键帧状态MotionLayout帮你算好了,你只需要给进度就行。
第二个进阶玩法,就是关键帧的自定义插值器。很多人写MotionScene的时候,都是直接写两个ConstraintSet,一个起始一个结束,默认就是匀速过渡。但实际产品需求里,哪有那么多多匀速动画?比如我们想要一个先快后慢的回弹效果,或者中间放大再缩小的弹性效果,默认配置就搞不定了。
这个时候,你可以给每个属性单独设置插值器。比如我要让一个图标在动画过程中,先放大到1.2倍再缩回到1倍,不用写三个ConstraintSet,直接在起始或者结束的Constraint里,给motion:attribute设置motion:transitionInterpolator就行了。
举个具体的写法,就是在
这样这个属性的变化就会走你指定的插值器,跟其他属性互不干扰。你甚至可以给不同的属性用不同的插值器,比如位移用加速,透明度用减速,做出来的动画效果比统一插值自然太多了。
还有很多人不知道,你甚至可以自定义Interpolator,直接写你的自定义类的全路径就能用,完全支持复杂的弹性插值,不用自己写代码动态改。
第三个很多人踩坑的点,就是MotionLayout和RecyclerView的嵌套冲突。这应该是进阶开发里最常见的问题了,你把RecyclerView放在MotionLayout里面,想要做滑动触发动画,要么RecyclerView滑不动,要么MotionLayout没反应,两大滑动控件打架,谁都不让谁。
解决这个问题其实很简单,MotionLayout本身提供了requestDisallowInterceptTouchEvent的处理,你只需要开一个配置就行了。在xml的MotionLayout标签里加上app:motionLayout_requiresNoNestedScrollingChild="false"不对,应该是app:nestedScrollingEnabled="true",然后给你的RecyclerView设置OnScrollChangeListener,在滚动的时候把你计算好的progress给MotionLayout,就像我刚才说的手动控制进度的方法。
如果还是有冲突,你可以自定义一个继承MotionLayout的子类,重写onInterceptTouchEvent方法,根据触摸位置和滑动方向自己判断要不要拦截事件。比如用户往下拉的时候,已经到列表顶部了,这个时候就让MotionLayout拦截,做下拉伸展的动画,往上滑的时候就把事件给RecyclerView,让列表滚动。
我之前做下拉刷新转场的需求就是这么处理的,完全没冲突,跟系统自带的滑动一样流畅。
第四个进阶玩法,就是用MotionLayout做共享元素转场。现在很多App的列表点击跳转详情,都要有个共享元素动画,从列表的小图变成详情页的大图,传统的SharedElementTransition有时候会跟Activity转场动画冲突,或者在某些机型上有卡顿掉帧的问题,用MotionLayout做反而更简单,效果也更稳。
具体怎么做呢?你只需要在起始Activity和目标Activity各放一个MotionLayout,起始页的结束关键帧就是目标页共享元素的位置,然后在startActivity的时候,把当前的progress传给目标页,目标页onCreate的时候,根据传过来的progress慢慢动画到结束状态就行。
比系统自带的共享元素好的地方是,你可以控制动画的整个过程,哪怕用户中途按返回,你也可以反向做动画退回去,不会出现闪屏的问题,而且支持同时给多个元素做动画,不光是位置大小,颜色透明度都能一起动,做出来的转场效果要细腻很多。
最后说一个很多人忽略的点,就是MotionLayout的性能问题。很多人说MotionLayout卡,其实是你用错了。MotionLayout本质上还是在绘制的时候更新属性,如果你一个布局里放了十几个动画同时动,肯定会掉帧,但是只要你做好两件事,就能保证流畅。
第一,不需要动的控件,把它放到ConstraintSet外面,或者标记为motion:constraintBehavior="unchanged",这样MotionLayout不会每次更新都去重新计算它的属性,节省很多性能。第二,如果你做的是跟手势联动的动画,打开app:motionStickyTransitions="true",它会自动优化掉中间不必要的刷新帧,滑动的时候更跟手。
其实MotionLayout出来这么多年了,早就不是那个实验性的东西了,现在Android开发的复杂动画需求,用它真的能省很多事,不用你写一堆属性动画,也不用处理各种手势冲突,大部分工作它都帮你做好了,你只需要关心关键帧的状态就行。
我现在做任何带交互动画的需求,第一反应都是用MotionLayout写,原来两三天才能搞定的复杂效果,现在大半天就能出来,而且稳定性比自己写的好太多。如果你们还停留在只会用它做简单转场的阶段,不妨试试今天说的这几个进阶技巧,说不定就能打开你自己的动画开发新世界。
Android开发,MotionLayout,Android动画,MotionLayout进阶,Android MotionLayout,安卓开发,动画开发,MotionScene,Android布局,嵌套滑动
[Q]:MotionLayout如何手动控制动画进度?
[A]:先禁用MotionLayout自带的触摸控制,设置`app:motionTouchEnable="false"`,之后就可以在自己的滑动、事件回调中,根据计算得到的0-1区间数值,调用`setProgress`来控制动画推进进度,适配自定义联动需求。
[Q]:MotionLayout能给不同属性设置不同的插值器吗?
[A]:可以,只需要在对应Constraint标签内,给单独属性添加`
[Q]:MotionLayout和RecyclerView滑动冲突怎么解决?
[A]:首先开启MotionLayout的`app:nestedScrollingEnabled="true`,采用手动控制进度的方式,在RecyclerView的滚动回调中给MotionLayout传递进度;如果仍有冲突,可以自定义MotionLayout子类,重写onInterceptTouchEvent,根据滑动方向和位置判断拦截逻辑即可。
[Q]:MotionLayout适合做共享元素转场吗?
[A]:非常适合,相比系统自带的共享元素转场,MotionLayout可以完整控制动画全过程,支持多属性同时过渡,中途返回也能流畅反向动画,不容易出现闪屏、卡顿问题,效果更稳定细腻。
[Q]:MotionLayout为什么会出现卡顿掉帧?
[A]:大多是使用方式不对导致的,不需要参与动画变化的控件,要标记为`motion:constraintBehavior="unchanged"`避免重复计算;跟手势联动的动画可以开启`motionStickyTransitions`优化刷新,就能有效提升流畅度。
[Q]:MotionLayout只能做预设好的转场动画吗?
[A]:不是,MotionLayout支持和自定义手势、嵌套滑动、滚动联动,只要手动更新进度就可以适配任意的自定义交互,不局限于预设的固定转场。
[Q]:入门MotionLayout之后,进阶需要先掌握哪些内容?
[A]:首先掌握手动控制进度、自定义插值器这两个基础进阶能力,然后解决最常见的滑动冲突问题,再进一步尝试共享元素转场、性能优化这些高阶玩法,就能应对大多数复杂动画需求。
[Q]:MotionLayout比自己写属性动画好在哪里?
[A]:MotionLayout把关键帧状态、插值器、手势冲突处理都封装好了,开发者只需要定义起始和结束状态,不需要编写大量的属性动画更新和事件处理代码,开发效率更高,稳定性也更好。
评论 (0)
