11

Material Components—预备役选手Transition

 3 years ago
source link: https://blog.csdn.net/eclipsexys/article/details/111399252
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Material Components—预备役选手Transition

Transition是Android Framework在4.4引入的一个全新的动画框架,可以说是非常古老了,那为什么我现在还要讲Transition呢,其实是想通过Transition来引入Material Design Motion。Transition实际上是MD Motion的基础,同时,也是现代化Android开发动画的基础。

国际惯例,官网镇楼。

https://developer.android.google.cn/training/transitions

其实从当时的设计来看,Google在提出Transition框架的时候,就已经准备通过申明式UI的方式来创建动画了,Transition框架的一个核心概念就是Scene,它描述的是一个场景,一个动画的状态值,通常情况下,是动画的起始状态值,有了这样一个起始态,再加上动画的具体类型,就可以完整的描述一个动画的执行过程,所以,在申明式的UI编程中,一切都是以Scene作为基础来进行的。

Transition的本质,实际上就是根据状态差异来生成属性动画,它实际上是对属性动画的抽象和封装。

下面通过一个简单的例子,来演示下如何使用Scene。

创建Scene Layout

首先,创建两个Scene Layout,用于描述动画的两个状态,这里简单的创建两个布局,一个布局在左上角和右下角展示一个ImageView,另一个布局在左下角和右上角展示一个ImageView,代码如下所示。

另一个布局,只有Item的位置发生的改变,id不变,这里就不贴重复代码了,要记住的是,对于一个元素的动画来说,在不同的Scene中,只要id不变,元素就不变,元素位置、属性的改变,这就是动画效果。

创建Scene Container

一般来说,在一个静态布局下,创建具有多个Scene的布局,会将动静部分分离,将要展示动画的部分,放置在一个Container中,便于管理,在前面创建好Scene Layout后,下面在主界面的xml中,创建它们的Container,代码如下所示。

通过TransitionManager驱动动画

在代码中,通过Scene.getSceneForLayout来创建Scene对象,再通过TransitionManager.go来加载指定的场景,代码如下所示。

当Scene发生改变时,TransitionManager会自动为其生成相应的动画效果。默认情况下,TransitionManager使用AutoTransition,即渐隐渐显合并位移动画,源码如下所示。

format,png
image-20201208195756453

所以这里还可以指定动画效果,例如我们只指定位置改变的动画,代码如下所示。

TransitionManager.go(scene2, ChangeBounds())

SDK内置了很多种类的动画效果,如图所示。

format,png
截屏2020-12-5.34.07

其中几种比较常用的解析如下。

  • ChangeBounds:检测view的位置边界,创建移动和缩放动画

  • ChangeTransform:检测view的scale和rotation,创建缩放和旋转动画

  • ChangeClipBounds:检测view的剪切区域的位置边界,和ChangeBounds类似,ChangeBounds指定的是剪切区域setClipBound中的rect

  • ChangeImageTransform:检测ImageView的大小、位置以及ScaleType,并创建相应动画

  • ChangeScroll:检测ViewGroup的Scroll,创建Scroll动画

  • Fade、Slide、Explode:检测View的Visibility,创建渐入、滑动、爆炸动画

创建Transition动画的几种方式

不论是transition的哪种使用方式,transition动画都有以下几种创建方式。

通过xml创建Transition动画

在res/transition下创建一个transitionSet的描述文件,代码如下所示。

在代码中,就可以通过类似LayoutInflater的方式来创建Transition,代码如下所示。

除了创建transitionSet的复合动画效果,创建单个的transition动画也是一样的,例如下面的代码。

同样可以通过TransitionInflater进行创建。

通过代码创建

对于单个的transition动画,可以通过下面的方式进行创建。

对于复合的transition动画,可以通过下面的方式进行创建。

前面提到到AutoTransition,也是继承的TransitionSet实现的复合动画。

不论是怎么使用transition动画,这些创建transition的方式都是可以混用的。

beginDelayedTransition

在前面的讲解中,TransitionManager.go是基于场景Scene切换而产生的动画效果。而Transition框架还提供一种类似自动检测的动画机制,这就是通过beginDelayedTransition来实现的。

下面通过代码来演示下。

当我们调用TransitionManager.beginDelayedTransition后,相当于在当前状态下打了个tag,将当前状态下的View属性,创建为初始Scene,在此之后View发生的属性改变,都将被生成新的Scene,从而产生动画效果,这也就是beginDelayedTransition这个API命名的原因。

在上面的代码中,在初始场景下,调用了beginDelayedTransition,创建的动画是changeBounds和explode,在这之后,修改了4个ImageView的属性——尺寸和visibility,并被作用了changeBounds和explode的动画效果,最后效果如下所示。

format,png
demo1

类似的,你还可以设置Slide这样的visibility动画效果,实现滑动的切换效果。

动画效果进阶

Slide

和Fade效果类似,它们都是继承自Visibility,它比Fade多了一些属性,除了可以设置属性动画的一些常见属性外,还可以设置Slide方向等属性。

Explode

Explode与Slide十分相似,但是元素将根据Transition Epicenter,辐射状移动,这个Epicenter可以通过setEpicenterCallback来设置。

在Explode中,动画通过TransitionPropagation计算每个动画的开始延迟,例如,默认情况下Explode使用CircularPropagation,动画的延迟取决于元素和Epicenter之间的距离,在代码中,可以通过setPropagation来设置自定义的TransitionPropagation,示例代码如下所示。

ChangeImageTransform

ChangeImageTransform会对图片进行Matrix变换,主要作用的是ImageView的ScalaType属性,通常情况下,ChangeImageTransform会和ChangeBounds配合使用,示例代码如下所示。

ChangeBounds

ChangeBounds用于改变元素的尺寸和坐标位置,默认情况下,是直线运动的,通过配置Path,可以设置ChangeBounds的曲线运动路径,示例代码如下所示。

ArcMotion可以设置minimumHorizontalAngle、minimumVerticalAngle、maximumAngle这样的属性来设置路径的具体形态。

当然,你也可以通过patternPathMotion来设置类似SVG的自定义路径。

setTransitionName

在使用beginDelayedTransition执行Transition动画时,可以通过设置transitionName来指定动画场景起始的相同元素,并让这些元素执行transition动画,例如为当前界面中的N个元素setTransitionName,当移除界面上全部元素后,只要setTransitionName的值相同,这些元素依然可以执行动画效果。

Transition界面切换

同样的,官网镇楼。

https://developer.android.google.cn/training/transitions/start-activity

Transition框架的一个重要使用场景,就是Activity和Fragment的切换动画。通常情况下,界面的切换动画分为两种类型——Content Transition和Shared Element Transition。

Content Transition

对于一次切换来说,A -> B,使用Transition的流程如下所示。

  • A.exitTransition Transition框架会先遍历A界面确定要执行动画的view(非共享元素view),执行A.exitTransition()前A界面会获取界面的start scene(view 处于VISIBLE状态),然后将所有的要执行动画的view设置为INVISIBLE,并获取此时的end scene(view 处于INVISIBLE状态).根据transition分析差异的不同创建执行动画。

  • B.enterTransition Transition框架会先遍历B界面,确定要执行动画的view,设置为INVISIBLE。执行B.enterTransition()前获取此时的start scene(view 处于INVISIBLE状态),然后将所有的要执行动画的view设置为VISIBLE,并获取此时的end scene(view 处于VISIBLE状态).根据transition分析差异的不同创建执行动画。

同理,在从B -> A,返回到A时,流程类似,只不过调用的方法不同。

  • A.reenterTransition

  • B.returnTransition

界面切换动画是建立在visibility的改变的基础上的,所以getWindow().setEnterTransition(transition);中的参数一般传的是Fade,Slide,Explode类的实例(因为这三个类是通过分析visibility不同创

简而言之。

A.exitTransition(): 从A->B时,A的退出动画

B.enterTransition(): 从A->B时,B的进场动画

B.returnTransition(): 从B->A时,B的退出动画

A.reenterTransition(): 从B->A时,A的进场动画

一般来说,如果不设置returnTransition和reenterTransition,那么这两个场景的动画,会使用exitTransition和enterTransition的反转动画。

下面就通过一个例子来演示下如何设置界面切换的动画效果。

要注意的是,Transition的实现有两个版本,platform版和AndroidX版,他们的差异在于,AndroidX版的Transition是后续会持续迭代的版本,但是不支持Activity和Window间的动画(至于为什么要这样设计,我在之前的文章中已经解释过了),platform版支持,但是后续不再维护。

首先,在Theme中设置Transition开关,如下所示。

format,png
image-20201209200311726

如果你使用的是Material Design Theme,那么这个值默认为true。

在代码中,可以设置如下所示。

window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)

设置Transition切换的两种方式

Transition的切换设定,可以在代码或者Theme中进行设置。

在Theme中,可以设置如下。

format,png
image-20201210175859139

在代码中,可以如下所示。

一般来说,如果是针对全局的设置,可以放在Theme中,但是在代码中设置,会更加灵活。

动画默认的持续时间,也是可以设置的,代码如下所示。

window.transitionBackgroundFadeDuration = 3000

Transition View & Transition Group

前面在讲解Content Transition的执行过程的时候,提到了在动画开始前,系统会调用ViewGroup.captureTransitioningViews函数,来获取需要进行Transition处理的View,如图所示。

format,png
image-20201210191105455

在默认情况下,Transition Group的判断如下所示。

format,png
image-20201210191206722

另外,在代码中,还可以通过View.setTransitionGroup(boolean)来主动将一部分View设置为Transition Group,从而在整体上执行动画。

为什么会有这样一个需求呢?其实很明显,Transition会遍历页面中的所有View,包括Toolbar、StatusBar这类的可能通用的组件,那么这个时候,在生成Transition切换动画的时候,就会产生一些不和谐的画面,比如这些通用组件的错位,所以,Transition框架提供了addTarget和excludeTarget方法来指定需要执行Transition切换动画的元素。

在代码中,设置如下。

这样就可以在执行Transition动画的时候,排除StatusBar和默认的ToolBar的动画效果,在xml中,可以在具体的Transition动画标签中设置,如下所示。

Transition Overlap

默认情况下,Transition的动画执行不是线性的,即并非A界面的退出动画执行完毕后才会执行B界面的进入动画,它们的执行是有一定的并行时间的(即默认为true),称之为Overlap,在代码中可以对这个行为进行控制,如下所示。

在代码中,可以设置如下。

启动Transition

在启动新的Activity时,需要传入一个特殊的Bundle对象,代码如下所示。

用这个方法替换传统的startActivity方法,就可以启动Transition切换动画了。

Shared Element Transition

对于Transition来说,Content Transition单纯的是两个页面间的切换动画,每个页面间都是单独的执行动画过程,而Shared Element Transition则不同,它标记了两个界面切换时需要共享动画效果的元素,让某些指定的元素,动画效果更佳丰富。

而对于执行过程中,Content Transition和Shared Element Transition的流程是一致的,只不过为了区分这两种不同的Transition类型,在原有命名的基础上,增加了sharedElement前缀,如下所示。

window.sharedElementExitTransition

不过一般情况下,sharedElementXXXXXTransition不用设置,因为默认是创建类似ChangeBounds的位移和尺寸改变动画。对于Content Transition来说,通常会使用Fade、Slide、Explode这类继承Visibility的Transition动画,而对于Shared Element Transition来说,动画执行前,需要指定要共享的元素的ID,并分析AB界面中,指定ID的元素的属性变化,从而生成属性动画,所以说,即使是Shared Element Transition,所有的动画效果实际上都是发生在B界面中的,共享的元素并没有在两个界面中传递。

共享元素这个属性的指定,就需要使用android:transitionName来进行指定。

启动Shared Element Transition与Content Transition类似,只是需要指定下共享元素的transitionName,代码如下所示。

延迟共享元素动画

在某些情况下,共享元素动画需要延迟一部分时间再执行,例如需要等布局渲染完毕,或者网络图片加载完成后再执行动画。这种场景下,就需要使用延迟加载的方式了,主要涉及的API有两个,即postponeEnterTransition()和startPostponedEnterTransition(),在需要延迟的场景下,先使用postponeEnterTransition暂停动画的执行过程,再在合适的场景下(例如在ViewTree渲染完成或者图片加载完成后),使用startPostponedEnterTransition恢复动画的执行。

这个API也经常用来解决Transition动画切换过程中闪烁的一些问题,例如在进入B界面的时候先暂停动画,在ViewTreeObserver中渲染完毕后再开启Transition动画执行。

SharedElementCallback

考虑这样一个场景,A界面通过RecyclerView展示数据列表,点击Item后跳转B界面,B界面通过ViewPager展示详细数据,当在B界面滑动数据后,回到A界面,A界面应该刷新数据到B界面访问到的数据,这里就需要用到Shared Element Transition提供的SharedElementCallback了。

在上面的场景下,给A界面设置setExitSharedElementCallback(SharedElementCallback),给B界面设置setEnterSharedElementCallback(SharedElementCallback),这样就可以实现更新的回调。

setExitSharedElementCallback(SharedElementCallback):在Activity exit和reenter时都会触发 setEnterSharedElementCallback(SharedElementCallback):在Activity enter和return时都会触发。

使用Transition动画的一般方式

先来看下这样一个效果,如图所示。

format,png
transition

结合这样一个例子,我们来看下一般如何处理transition动画,首先,要对动画过程进行拆解,无论做什么动画,这都是第一步。

在使用Transition动画时,大部分的场景都是Content Transition和Shared Element Transition同时使用的,这个例子也是这样,我们可以发现,Image和Text,使用的是Shared Element Transition,而界面B的其它部分,使用的是Content Transition,而界面A,通常不用设置Transition。

Shared Element Transition部分

sharedElementEnterTransition通常也不用设置,默认会使用ChangeBounds,当然,你也可以修改ChangeBounds的默认行为,例如interpolator,arcMotion等。这里需要执行共享元素的Item,就是Image和Text,所以在B界面的XML中,需要指定对应的transitionName即可。界面B的布局代码如下所示。

界面A的代码比较简单,如下所示。

Content Transition部分

下面的内容和中间的文本,使用的是Content Transition,只需要针对这些元素,做相应的enterTransition即可,代码如下所示enter_anim.xml。

TransitionListener

所有的Transition,都可以设置TransitionListener来监听其执行过程,代码如下所示。

例如可以在Transition结束后,执行其他的属性动画等等。

在B界面退出的时候,我这里使用了新的动画效果,即设置了returnTransition,并非默认效果,而且这里有一点需要注意,那就是enterTransition时,是针对单独的元素设置的,而returnTransition,则是分成了上下两个部分进行动画(主要是下部分),所以这里需要使用到前面提到的TransitionGroup的概念。在enterTransition的时候,TransitionGroup要设置为false,在returnTransition的时候,TransitionGroup要设置为true(因为ViewGroup只要设置了background或者TransitionName,就会被判断为TransitionGroup为true)。代码如下所示return_anim.xml。

在分解完这些动画后,就可以将整个过程串联起来了,界面A代码如下所示。

界面B,代码如下所示。

通过这种方式,就完成了Transition动画的一般开发过程,总结一下,主要就是下面几个步骤。

  • 拆解动画:将过渡动画拆分成Content Transition和Shared Element Transition

  • 针对Content Transition,对每个元素编写相应的动画

  • 针对Shared Element Transition,确定好TransitionGroup后,指定两个页面之间的transitionName

  • 组装动画:借助生命周期回调等状态,将动画串联起来

自定义Transition

https://developer.android.google.cn/training/transitions/custom-transitions

官网中其实已经给我们提供了非常详细的说明,同时,参考默认的Slide、Fade这些SDK默认Transition的实现,我们可以很方便的自定义,下面就以一个改变background的Transition为例进行讲解。

首先,需要继承Transition,实现下面三个方法。

  • captureStartValues

  • captureEndValues

  • createAnimator

前面两个方法基本都是设置需要自定义的属性值,重要的是最后一个方法,创建属性动画。

在前面两个函数中,TransitionValues起到了一个容器的作用,保存了values和views,指定了需要作用的对象和值

使用和系统默认的Transition一样,示例代码如下所示。

可以发现,实际上Transition就是为不同的属性创建属性动画而已,从自定义Transition就可以看出它的本质。

最后,推荐几个自定义Transition开源库。

https://github.com/HJ-Money/MTransition

https://github.com/ImmortalZ/TransitionHelper

https://github.com/lgvalle/Material-Animations

https://github.com/andkulikov/Transitions-Everywhere

预备役选手?

好了,终于到最后了,讲了这么多Transition的使用方法,那么为什么我还叫他预备役选手呢?这就是因为,在Material Design Component中,对Motion进行了进一步的封装,即:

  • Container transform

  • Shared axis

  • Fade through

这样四种封装好的Motion,而Transition,则正是它们的基础原理。

所以,Transition现在虽然用的不多,但是掌握了它的原理,才能更好的开启MDC Motion之旅。

最后,介绍下我的网站:https://xuyisheng.top/  点击原文,一键直达

Flutter & Android 关注 《Android群英传》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK