添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
2021-03-14 12:37

使用运动布局(和协同程序)探索复杂的多步动画。

https://thumbs.gfycat.com/HairyWellwornGelding-mobile.mp4

运动布局是在动画、过渡、复杂动作以及你拥有的功能上的新助手。本文中,我们将研究运动布局和Coroutines是如何帮助我们构建多步动画的。

上一篇文章深入探讨了没有使用运动布局的不同动画和小部件,我希望在阅读本文之前你能看看之前的文章,因为:

1.在本文中,我们将只讨论过滤器工作表转换,而不会讨论适配器、选项卡和其他动画。

2.你会理解和欣赏使用运动布局编写这些动画和不使用时的区别。

Android上复杂的UI/动画
如何在Adnroid上编写复杂的多步动画.
proandroiddev.com

TLDR? 在Github上查看源代码。 它有充分的文档记录,并包含两者的代码,包括使用和不使用运动布局两种情况。 在PlayStore上下载该应用程序, 或构建源代码以演示该应用程序。(不要忘记选中导航抽屉中的 “使用 运动布局 复选框)。

什么是运动布局?快速介绍…

简而言之, 运动布局 是一个允许你可以轻松在两个ConstraintSet之间进行转换的 ConstraintLayout

<ConstraintSet> 包含每个视图的所有约束和布局属性。

<Transition> 指定要在其之间进行过渡的起始ConstraintSets。

将所有这些都放入 <MotionScene> 文件中,你就可以 拥有一个运动布局啦!

随着布局和动画变得越来越复杂,MotionScene也变得越来越复杂,接下来我们将看一下这些组件。

了解有关运动布局的更多信息:

#1 Nicolas Roard的 运动布局 系列 简介

#2 James Pearson的高级和实用的 运动布局 演讲。

#3 运动布局 上的Android 官方 开发人员指南

所有动画放在一起,该项目的运动场景文件包含 10个 约束集 9个 过渡 。以下视频演示了所有的ConstraintSets和Transitions,我们将寻找到 4个动画

打开过滤器表:
Set1→Set2→Set3→Set4 关闭滤纸:
Set4→Set3→Set2→Set1 应用过滤器:
Set4→Set5→Set6→Set7 删除过滤器:
Set7→Set8→Set9→Set10

注意: 背景中的RecyclerView Items动画不是 运动布局 的一部分。在本文的后面,我们将看到如何 使用 运动布局 编排外部动画。

本文中的每个动画(GIF)都会在其下方显示约束集详细信息(Ex: Set 4, Transitioning…, Set 5, etc),以便在阅读和浏览源代码时更易于操作。

约束集是运动布局执行动画所需的 构造块 。你可以在此处指定所有约束、布局属性等。

一个 <ConstraintSet> 必须包含一个 <Constraint> 元素,并且该元素带有每个你想要将场景动画化的布局属性

分解你的元素

你可以在 <Constraint> 元素中指定所有布局属性。 但是 对于更复杂的动画,你应该使用 <Layout> <PropertySet> <Transform> <Motion> <CustomAttribute> 标签对其进行分解。

这允许你仅覆盖所需的属性,而无需重复写下所有属性。

1_P5OuFMZvsxccOKl5lgSqQg.png 1250×384 174 KB

app:deriveConstraintsFrom =“…”

deriveConstraintsFrom 是一个非常有用的标签,它允许你继承其他的 <ConstraintSet> 。这样,你不必重写所有视图/约束/属性,而只需重写要设置动画的视图/约束/属性就可以了。

将其与之前分解的 <Constraint> 元素结合起来,你将获得包含你想更改部分的简洁版约束集。

在该项目中,10个约束集中的每一个都从先前的集合派生而来,并且仅修改需要动画的内容。例如:在以下转换中,关闭图标旋转是通过从 Set5 中导出所有约束并仅在 Set6 中应用旋转来完成的。

警告:覆盖 其中一个元素时,该元素中的所有属性都会被覆盖,因此你必须从该元素复制其他属性。

必要时展开你的视图

运动布局只能与它直接初始视图一起使用,并且没有嵌套视图。

例如,在此动画中,过滤器图标看起来像是圆形FAB( CardView )的一部分,但是它们被分成不同的视图,因为在动画中它们各自都有自己的工作。

同样,fab的高程从 Set1 → Set2 设置为动画,图标必须放在更高的位置才能显示。图标的这种不理想的效果是投射了自己的阴影。为了防止这种情况,我们可以使用:

android:outlineProvider="none"

阴影是由视图轮廓提供者创建的。如果将其设置为 none ,则不会创建阴影。

自定义属性

运动布局提供了我们要设置动画的大多数基本属性,但是它不能提供 一切 。例如,自定义视图可能需要设置其他属性的动画。

< CustomAttribute >通过允许你在视图中使用任何 设置器 来弥合这种差距。它使用反射来调用方法并设置值。

<CustomAttribute 
    app:attributeName =“ radius” 
    app:customDimension =“ 16dp” />

注意:你必须使用设置器名称,而不是xml attr名称。例如,CardView有一个 setRadius()法,而xml中的方法是 app:cardCornerRadius 。CustomAttribute应该引用设置器-“ radius”。

“隐形” vs“消失”

设置从 invisible / gonevisible 的可见性动画时,需要留意这种差异。

gone → visible 将设置 Alpha 动画 并缩放

invisible → visible仅对alpha进行 动画处理。

过渡是2个约束集之间的连接,它们指定了在其之间进行 转换 的开始和结束状态。

<Transition 
    app:constraintSetStart =“ @ id / set1” 
    app:constraintSetEnd =“ @ id / set2” 
    app:motionInterpolator =“ linear” 
    app:duration =“ 300” />

你还可以使用 <OnClick><OnSwipe> 元素在过渡中指定滑动和单击相关的功能,但是由于我们正在观察的10套动画不太需要 所以 我们将不在本文 中介绍。

我们可以使用 app:motionInterpolator 来为转换指定插值器。可用选项为 lineareaseIneaseOuteaseInOut 。当你把它们比作 AnticipateInterpolatorBounceInterpolator 等,那是不够的。

https://cubic-bezier.com/#0,1,.5,1

对于这些情况,你可以使用 cubic() 选项,在其中可以使用 贝塞尔曲线 定义自己的插值器。你可以创建自己的贝塞尔曲线,并在cube-bezier.com上获取值。

可以使用以下方法进行设置:

`app:motionInterpolator=”cubic(0,1,0.5,1)`

有时,仅具有开始和结束状态是不够的。对于更复杂的动画,我们可能希望更详细地指定过渡的过程。关键帧可帮助我们在过渡中指定 “检查点”, 在该过渡中我们可以在任何给定时间更改视图的任一属性。

文章“运动布局中定义运动路径”深入探讨了关键帧及其使用方法。

:有关键帧… :无关键帧

左侧的动画具有 9个关键帧 ,而右侧的动画则没有关键帧。

如你所见,它们的开始(设置4)和结束(设置5)相同。但是,通过使用关键帧,我们可以更好地控制每个元素在过渡期间发生的变化。

结构化关键帧

每个 <Transition /> 都可以具有一个或多个 <KeyFrameSet /> 已经指定的所有关键帧的元素。对于此项目,仅用了 <KeyPosition /><KeyAttribute /> 元素。

motionTarget 指定哪个视图受关键帧影响。 framePosition 指定在过渡期间何时应用关键帧(0-100) <KeyPosition /> 用于指定宽度、高度和x,y坐标的变化 <KeyAttribute /> 用于指定其他任何改动, 包括 CustomAttributes

framePosition = 0 vs 1

有时,我们想在动画的一 开始 就更改属性。在普通动画中,可以通过使用 animator.doOnStart{...} 或类似方法实现。让我们尝试通过关键帧实现相同的效果。

:framePosition = 1… :framePosition = 0

在此特定动画中,当用户单击“过滤器”按钮时,动画首先将fab(CardView)更改为一个圆形并缩小其大小。

这里的问题是,在动画开始时,何时将 framePosition = 0 用来更改值,运动布局不会记录它。

因此,如果你希望关键帧在任何过渡开始时都指定一些内容,请改用 framePosition = 1

<KeyAttribute 
    app:motionTarget =“ @ id / fab” 
    app:framePosition =“ 1”> 
    <CustomAttribute 
        app:attributeName =“ radius” 
        app:customDimension =“ 600dp” /> 
</ KeyAttribute>

必要时使用自定义视图

CustomAttributes 的可用性允许我们使用自定义视图灵活的进行布局。

例如,此动画中的许多过渡都涉及到FAB( CardView )来扩大和缩小 为一个圆形 。问题是,要将CardView保持为圆形 cornerRadius must be <= size/2 。通常情况下,使用类似 ValueAnimator 的方 法很容易,因为我们一直都知道所有值。

但是, 运动布局 使所有计算都远离了我们。因此,要实现这一点,我们必须引入一个新的视图:

CircleCardView 通过将半径限制为最大size / 2来处理这种情况。现在,当 运动布局 调用设置器时(还记得 CustomAttributes 吗?),我们就不会遇到问题了。

编排多步动画

当前,运动布局没有允许受控的多步过渡API。我们可以使用 autoTransition ,但是有很大的局限性(我们将在后面讨论)。在伪代码中,以下是你要执行的操作:

// Transitioning from set1 -> set2 -> set3 -> set4
motionLayout.setTransition(set1, set2)
motionLayout.transitionToEnd()
motionLayout.doOnEnd {
    motionLayout.setTransition(set2, set3)
    motionLayout.transitionToEnd()
    motionLayout.doOnEnd {
        motionLayout.setTransition(set3, set4)
        motionLayout.transitionToEnd()
        motionLayout.doOnEnd {

这就很恶心了,又变成了可怕的回调地狱。 另一方面协程可以 帮助我们将异步回调代码转换为线性代码。

运动布局.awaitTransitionComplete()

克里斯·班纳斯(Chris Banes)撰写的有关“在视图暂停”的文章是有关如何在与视图相关的代码中实现协程的必读文章。

暂停视图 — 示例
A worked example from the Tivi app
medium.com

他向我们介绍了 awaitTransitionComplete() ,这是一个 暂挂函数 ,可隐藏所有侦听器,使你可以轻松地使用协程完成转换:

1_yFk2YDqAfkC_pCugcEg5Og.jpg1250×431 162 KB

注意:所述的 awaitTransitionComplete() 扩展方法使用修饰 运动布局 ,它能设置多个侦听器而不是只有一个(要被设置功能的请求)。

autoTransition 是在 没有协程时 实现多步过渡的最简单方法。假设我们要从 Set7 → Set8 → Set9 → Set10 实现 “ Removing Filters” 动画。

现在,如果这样做 运动布局 .transitionToState(set8) ,运动布局从 Set7 → Set8 开始过渡,到达 Set8 时,它会 自动转换Set9 ,并与 Set10 类似。

当运动布局在 constraintSetStart中达到指定的ConstraintSet时,autoTransition将自动执行过渡。

自动转换并不完美

如果再次观看动画,你会注意到有一个动画正在进行,适配器项在后台。为了与运动布局转换 并行 完成这些动画,我们必须使用协程,仅使用 autoTransition 不能正确设置它们的时间。

private fun unFilterAdapterItems(): Unit = lifecycleScope.launch {
  // 1) Set7 -> Set8 (Start scale down animation simultaneously)
  motionLayout.transitionToState(R.id.set8)
  startScaleDownAnimator(true) // Simulataneous
  motionLayout.awaitTransitionComplete(R.id.set8)
  // 2) Set8 -> Set9 (Un-filter adapter items simultaneously)
  (context as MainActivity).isAdapterFiltered = false // Simulataneous
  motionLayout.awaitTransitionComplete(R.id.set9)
  // 3) Set9 -> Set10 (Start scale 'up' animation simultaneously)
  startScaleDownAnimator(false) // Simulataneous
  motionLayout.awaitTransitionComplete(R.id.set10)

标有 //Simultaneous 的线与正在发生的过渡同时发生。

由于 autoTransition 不能从一个过渡到下一个,因此 awaitTransitionComplete() 仅在过渡完成时通知我们。它实际上 并不会 等到转换的结束。这就是为什么我们一开始只使用 transitionToState() 一次的原因。

多步前进和后退过渡

自动转换与协同程序相结合可帮助我们实现对多步转换的控制。

但是,如果我们想在每次过渡时后退 Set4 → Set1 ,该怎么办?

例如,反转 特定的 过渡。使用 transitionToStart() 完成 Set4 → Set3 。如果我们使用 autoTransition ,那么就会因为autoTransition而自动动画到 Set3 ,然后再自动回到 Set4

由于未使用 autoTransition ,因此打开过滤器表的代码与上一节中看到的代码略有不同。

/** Order of animation: Set1 -> Set2 -> Set3 -> Set4 */
private fun openSheet(): Unit = lifecycleScope.launch {
  // Set the start transition. This is necessary because the
  // un-filtering animation ends with set10 and we need to
  // reset it here when opening the sheet the next time
  motionLayout.setTransition(R.id.set1, R.id.set2)
  // 1) Set1 -> Set2 (Start scale down animation simultaneously)
  motionLayout.transitionToState(R.id.set2)
  startScaleDownAnimator(true) // Simultaneous
  motionLayout.awaitTransitionComplete(R.id.set2)
  // 2) Set2 -> Set3
  motionLayout.transitionToState(R.id.set3)
  motionLayout.awaitTransitionComplete(R.id.set3)
  // 3) Set3 -> Set4
  motionLayout.transitionToState(R.id.set4)
  motionLayout.awaitTransitionComplete(R.id.set4)
  • 每次等待后我们都必须使用 transitionToState() 。之前没有必要这样做,因为 autoTransition 无需等待就可以经过所有对象。而在这里,我们必须手动进行。
  • 注意,等待之后我们不会每次都使用 setTransition() 。这是因为 运动布局 将根据 transitionToState() 中提到的当前约束集和ConstraintSet来标识要使用的转换。
  • 收盘动画(反向)

    /** Order of animation: Set4 -> Set3 -> Set2 -> Set1 */
    private fun closeSheet(): Unit = lifecycleScope.launch {
      // We don't have to setTransition() here since current transition is Set3 -> Set4.
      // transitionToStart() will automatically go from:
      // 1) Set4 -> Set3
      motionLayout.transitionToStart()
      motionLayout.awaitTransitionComplete(R.id.set3)
      // 2) Set3 -> Set2
      motionLayout.setTransition(R.id.set2, R.id.set3)
      motionLayout.progress = 1f
      motionLayout.transitionToStart()
      motionLayout.awaitTransitionComplete(R.id.set2)
      // 3) Set2 -> Set1 (Start scale 'up' animator simultaneously)
      motionLayout.setTransition(R.id.set1, R.id.set2)
      motionLayout.progress = 1f
      motionLayout.transitionToStart()
      startScaleDownAnimator(false) // Simultaneous
      motionLayout.awaitTransitionComplete(R.id.set1)
    

    由于所有 <Transition> 元素都是基于正向的,因此我们必须添加几行代码使其可逆。它的本质是:

    // Set the transition to be reversed (MotionLayout can only detect forward transitions).
    motionLayout.setTransition(startSet, endSet)
    // This will set the progress of the transition to the end
    motionLayout.progress = 1f
    // Reverse the transition from end to start
    motionLayout.transitionToStart()
    // Wait for transition to reach the start
    motionLayout.awaitTransitionComplete(startSet)
    // Repeat for every transition...
    

    :heavy_check_mark:现在,这使我们能够反向完成多个转换,同时保持并执行其他操作的能力。

    结论—是否使用运动布局?

    运动布局 与协程结合使用,可以非常轻松地用很少的代码来实现非常复杂的动画, 同时保持平面视图层次!

    运动布局十分简洁,并为我们提供了很多有用的东西。使用协程和即将推出的IDE编辑器,其前途无可限量。

    希望你会喜欢这篇文章:smiley:!感兴趣的话,请查看源代码!!

    原文作者 Nikhil Panju
    原文链接 https://proandroiddev.com/complex-ui-animations-on-android-featuring-motionlayout-aa82d83b8660

    回复·0
    浏览量·1142
    推荐阅读
    相关专栏
    开发者实践
    182 文章
    本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。
    创建新话题
    • media-sdk
    • video
    • network
    • web-sdk
    • datahub
    • live-streaming
    • audio
    • android
    • sfu
    • websocket
    • 编解码
    • sdp
    • demo
    • official
    • swift
    • objective-c
    • java
    • javascript
    • kotlin
    • reactnative
    • 小程序
    • 工具
    • cocos
    • unity
    • linux
    • cpp
    • python
    • flutter
    • gdx
    • electron
    • c-sharp
    • release-note
    • 精华资源
    • 社区投稿
    • qt
    • 集成问题
    • 产品功能咨询
    • 文档
    • 其它
    • 深圳
    • 意见反馈
    • ios
    • macos
    • web
    • windows
    • 云录制
    • 本地服务端录制
    • stun
    • conturn
    • apicloud
    • solo
    • rtc
    • 视频会议
    • 泛行业
    • 在线教育
    • 推流
    • restful-api
    • 泛平台
    • rtm
    • 音频问题
    • 第三方功能咨询
    • 录制
    • 社交娱乐
    • 账号账单计费
    • 互动游戏
    • 质量问题
    • demo问题
    • nodejs
    • 需求及优化建议
    • 云代理
    • 文档问题
    • token
    • 定制开发服务咨询
    • ncs
    • uniapp
    • beta-program
    • rts
    • cocos-creator
    • web-ng
    • 精选文章
    • 内容共建
    • mediaplayer
    • 产品评测
    • sdk
    • 容器
    • rte
    • 实时消息
    • 开源
    • 社交游戏
    • 视频社交
    • 视频编码
    • 社交直播
    • 高清视频
    • h5
    • webrtc
    • ui
    • golang
    • 水晶球
    • vr
    • rtsa
    • 开发者吐槽会
    • voice
    • apaas
    • 官方faq
    • 小知识
    • 实时互动技术展望
    • 低延时高音质
    • 白板
    • webassembly
    • podcast
    • 编码人声
    • workshop
    • science-video
    • basictutorials
    • elementary-webrtc
    • elementary-audio
    • elementary-viau
    • intermediate-tutoria
    • intermediate-auvi
    • speech-recognition
    • player
    • video-editor
    • codec
    • intermediate-webrtc
    • intermediate-sr
    • elementary-codec
    • react
    • advanced-audio
    • intermediate-audio
    • 前端
    • 音视频
    • 架构
    • ai
    • css
    • 测试
    • metaverse
    • 问题反馈
    • science-rte
    • codectech
    • audiotech
    • videotech
    • rtetech
    • audio-practice
    • video-practice
    • rte-practice
    • 元宇宙
    • 音频
    • techheight
    • t
    • basic-agora
    • level2
    • api
    • web3
    • spring
    • 创业
    • 后端
    • rte-ng-lab
    • webrt
    • webrtc-音视频
    • rte2022编程大赛
    • 安全
    • 产品创新
    • github
    • 代码实验室

    No data

    回复