基于Kotlin和RxJava的Android动画



哈Ha! 去年,来自PSPDFKit的IvanŠkorić在MBLT DEV上发表了有关基于Kotlin和RxJava库在Android中创建动画的报告。

现在,我在使用项目时使用了报告中的技术,这些技术大有帮助。 过场动画是报告和视频的抄本,现在您可以利用这些技巧。

动画制作


在Android中,默认情况下有四个适用的类:

  1. ValueAnimator-此类为运行动画提供了一种简单的同步机制,该动画计算动画值并为View设置它们。
  2. ObjectAnimatorValueAnimator的子类,它允许您支持对象属性的动画。
  3. AnimatorSet用于创建动画序列。 例如,您有一系列动画:

    1. 查看屏幕左侧的叶子。
    2. 完成第一个动画后,我们要为另一个View执行外观动画,等等。
  4. ViewPropertyAnimator-自动为选定的View属性启动并优化动画。 我们将主要使用它。 因此,我们将使用此API,然后将其放入RxJava中作为反应式编程的一部分。


ValueAnimator


让我们分析ValueAnimator框架。 用于更改值。 您可以通过ValueAnimator.ofFloat为原始浮动类型指定一个范围为0到100的值。指定一个Duration值并开始动画。
考虑一个例子:

val animator = ValueAnimator.ofFloat(0f, 100f) animator.duration = 1000 animator.start() animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { val animatedValue = animation.animatedValue as Float textView.translationX = animatedValue } }) 

在这里,我们添加了UpdateListener ,并且每次更新时,我们都会水平移动View并将其位置从0更改为100,尽管这不是执行此操作的好方法。

ObjectAnimator


另一个动画实现示例是ObjectAnimator:

 val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start() 

我们给他一个命令,将特定的View参数更改为所需View的指定值,并使用setDuration方法设置时间。 底线是您的类必须具有setTranslationX方法,然后系统将通过反射找到该方法,然后对View进行动画处理。 问题是这里使用反射。

动画师


现在考虑AnimatorSet类:

 val bouncer = AnimatorSet() bouncer.play(bounceAnim).before(squashAnim1) bouncer.play(squashAnim1).before(squashAnim2) val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f) fadeAnim.duration = 250 val animatorSet = AnimatorSet() animatorSet.play(bouncer).before(fadeAnim) animatorSet.start() 

实际上,这不是很方便使用,尤其是对于大量对象。 如果要制作更复杂的动画,例如,设置动画外观之间的延迟,并且要执行的动画越多,则控制动画就越困难。

ViewPropertyAnimator


最后一个类是ViewPropertyAnimator 。 它是给View设置动画的最佳类之一。 这是一个很棒的API,用于介绍您运行的一系列动画:

 ViewCompat.animate(textView) .translationX(50f) .translationY(100f) .setDuration(1000) .setInterpolator(AccelerateDecelerateInterpolator()) .setStartDelay(50) .setListener(object : Animator.AnimatorListener { override fun onAnimationRepeat(animation: Animator) {} override fun onAnimationEnd(animation: Animator) {} override fun onAnimationCancel(animation: Animator) {} override fun onAnimationStart(animation: Animator) {} }) 

我们启动ViewCompat.animate方法,该方法返回ViewPropertyAnimator ,对于动画translationX我们将值设置为50, translatonY参数设置为100。然后我们指定动画的持续时间以及插值器。 插值器确定动画的出现顺序。 此示例使用了一个插值器,该插值器可加快动画的开始速度,并在结尾处增加减速速度。 我们还添加了开始动画的延迟。 另外,我们还有一个AnimatorListener 。 使用它,您可以订阅动画过程中发生的某些事件。 该接口有4种方法: onAnimationStartonAnimationCancelonAnimationEndonAnimationRepeat

通常,我们只对完成动画感兴趣。 在API级别16中
加上endAction:

 .withEndAction({ //API 16+ //do something here where animation ends }) 

在其中,您可以定义Runnable接口,并在完成显示特定动画后执行一个动作。

现在对一般动画的创建过程进行一些评论:

  1. start()方法是可选的:调用animate()方法后,就会引入一系列动画。 配置ViewPropertyAnimator后 ,系统将在准备好动画后立即开始动画。
  2. 只有一个ViewPropertyAnimator类只能为特定的View设置动画。 因此,例如,如果要执行多个动画,则需要移动某些东西,同时又要增加其大小,则需要在一个动画师中指定它。

我们为什么选择RxJava?


让我们从一个简单的例子开始。 假设我们创建一个fadeIn方法:

 fun fadeIn(view: View, duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(view) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } } 

这是一个相当原始的解决方案,为了将其应用于您的项目,您需要考虑一些细微差别。

我们将创建一个CompletableSubject ,它将用于等待动画完成,然后使用onComplete方法将消息发送给订阅者。 要顺序运行动画,您需要立即启动动画,而不是立即订阅动画。 这样,可以依次运行多个反应式动画。

考虑动画本身。 在其中,我们传输将在其上执行动画的视图,并指出动画的持续时间。 由于此动画是外观,因此我们必须指定透明度1。

让我们尝试使用我们的方法并创建一个简单的动画。 假设我们在屏幕上有4个按钮,并且我们想为它们添加一个持续时间为1秒的动画:

 val durationMs = 1000L button1.alpha = 0f button2.alpha = 0f button3.alpha = 0f button4.alpha = 0f fadeIn(button1, durationMs) .andThen(fadeIn(button2, durationMs)) .andThen(fadeIn(button3, durationMs)) .andThen(fadeIn(button4, durationMs)) .subscribe() 

结果就是这样简洁的代码。 使用andThen运算符,可以顺序运行动画。 当我们订阅它时,它将doOnSubscribe事件发送到Completable ,这是执行队列中的第一个事件。 完成后,他将订阅第二,第三等等。 因此,如果在某个阶段发生错误,则整个序列都会引发错误。 您还必须在动画开始之前指定alpha 0,以使按钮不可见。 这是它的外观:


使用Kotlin ,我们可以使用扩展名:

 fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } } 

对于View类,添加了扩展功能。 将来,无需将View参数传递给fadeIn方法。 现在,您可以在方法内用this关键字替换对View的所有调用。 这就是Kotlin的能力

让我们看看动画链中对此函数的调用是如何变化的:

 button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe() 

现在,代码看起来更容易理解了。 它清楚地表明,我们希望将具有一定持续时间的动画应用于所需的显示。 使用andThen运算符, 我们创建了到第二个,第三个按钮的依序动画序列,依此类推。

我们总是指示动画的持续时间,该值对于所有显示都是相同的-1000毫秒。 Kotlin再次救援。 我们可以设置一个默认的时间值。

 fun View.fadeIn(duration: Long = 1000L): 

如果未指定duration参数,则时间将自动设置为1秒。 但是,如果我们希望数字2的按钮将此时间增加到2秒,则只需在方法中指定此值:

 button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe() 

运行两个动画


我们能够使用andThen运算符运行一系列动画。 如果我需要同时运行2个动画怎么办? 为此, RxJava中有一个mergeWith运算符,该运算符使您可以以同时启动Completable元素的方式组合它们。 该语句开始所有元素,并在显示最后一个元素后结束。 如果将andThen更改为mergeWith则会得到一个动画,其中所有按钮同时出现,但是按钮2的显示时间比其他按钮长:

 button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe() 


现在我们可以对动画进行分组。 让我们尝试使任务复杂化:例如,我们希望按钮1和按钮2同时出现,然后按钮3和按钮4同时出现:

 (button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe() 

我们将第一个和第二个按钮与mergeWith运算符结合在一起,对第三个和第四个按钮重复该操作,然后使用andThen运算符依次启动这些组。 现在,我们将通过添加fadeInTogether方法来改进代码:

 fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) } 

它将允许您同时为两个View运行fadeIn动画。 动画链如何变化:

 fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe() 

结果是以下动画:


考虑一个更复杂的例子。 假设我们需要显示一个具有一定延迟的动画。 interval语句将帮助:

 fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) } 

它将每100毫秒生成一个值。 每个按钮将在100毫秒后出现。 接下来,我们指示另一个将发出按钮的Observable。 在这种情况下,我们有4个按钮。 我们使用zip运算符。

图片

摆在我们面前的是事件的流:

 Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() }) 

第一个对应于timeObservable 。 此Observable会定期生成数字。 假设将是100毫秒。

第二个Observable将生成一个视图。 zip运算符等待,直到第一个对象出现在第一个线程中,然后将其从第二个线程连接到第一个对象。 尽管第二个线程中的所有这四个对象将立即出现,但他将等待直到这些对象开始出现在第一个线程中。 因此,第一个流中的第一个对象将以我们的视图的形式连接到第二个对象中的第一个对象,并且在100毫秒后,当出现新对象时,操作员会将其与第二个对象合并。 因此,视图将出现一定的延迟。

让我们处理RxJava中的BiFinction 。 此函数接收两个对象作为输入,对其进行一些操作,然后返回第三个对象。 由于我们调用了fadeIn动画并订阅subscription ,因此我们希望花费时间查看对象并获得Disposable 。 时间的价值对我们并不重要。 结果,我们得到了这个动画:


瓦诺格


我将向您介绍Ivan开始为MBLT DEV 2017开发的项目

由Ivan开发的库提供了各种动画外壳。 上面我们已经考虑了这一点。 它还包含可以使用的现成动画。 您将获得一套通用的工具来创建自己的动画。 该库将为您提供用于响应式编程的更强大的组件。

考虑使用示例的库:

 fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() } 

假设您要创建一个出现的动画,但是这次出现了AnimationCompletable而不是Completable对象。 该类继承自Completable ,因此现在出现更多函数。 先前代码的一个重要功能是无法取消动画。 现在,您可以创建一个AnimationCompletable对象,该对象使动画在我们取消订阅后立即停止。

使用AnimationBuilder-库类之一创建新兴的动画。 指定将对动画应用的视图。 本质上,此类复制ViewPropertyAnimator的行为,但区别在于输出是流。

接下来,将alpha设置为1f,持续时间为2秒。 然后我们收集动画。 一旦调用build语句,就会出现动画。 我们为动画分配不可变对象的属性,因此它将为启动保存这些特征。 但是动画本身不会开始。

调用toCompletable ,这将创建一个AnimationCompletable 。 它将把该动画的参数包装在一种外壳中,以便进行响应式编程,并且一旦订阅它,它就会启动动画。 如果在处理完成之前将其关闭,则动画将结束。 您现在还可以添加一个回调函数。 您可以编写运算符doOnAnimationReadydoOnAnimationStartdoOnAnimationEnd等:

 fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } } 

在此示例中,我们展示了使用AnimationBuilder多么方便,并且在开始动画之前更改了View的状态。

举报视频


我们研究了使用Kotlin和RxJava创建,合成和调整动画的选项之一。 这里是一个指向项目的链接,该项目描述了基本动画及其示例,以及处理动画的主要外壳。

除了解密之外,我还分享了该报告的视频:


演讲者MBLT DEV 2018


MBLT DEV 2018之前,还有两个多月的时间。 我们将有以下表演:

  • Laura Morinigo,Google开发人员专家
  • 片段播客的作者Kaushik Gopal
  • 巴杜Artyom Rudoi
  • 狄娜·西多罗娃(Dina Sidorova),谷歌(Google)

明天票价将改变。 立即注册

Source: https://habr.com/ru/post/zh-CN418383/


All Articles