Android代码实现新年贺卡动画示例详解

作者:dora 时间:2022-09-11 00:28:49 

引言

什么?兔了个兔?吐了还要吐?首先今天,我们自己用android程序实现一个兔年的新年贺卡。下面就是见证美好的时刻,上效果。

Android代码实现新年贺卡动画示例详解

好,我们来使用Android动画的知识,来实现这样一个动画效果吧。

需要使用到的知识点

架构设计、Android视图动画、TypeEvaluator、Path、组合模式、代理模式。

思路分析

我们回顾动画的种类,补间动画、帧动画、属性动画以及Android View自带的视图动画。我们今天自己基于属性动画来打造一个山寨版的Android视图动画吧。我们可以从平移动画、缩放动画、旋转动画和透明度动画中抽象出一个基类Action类。我是不会告诉你这个类的命名我是抄的cocos2d的。然后我们扩展Action类,实现这四种动画,再作用在View上。这样就可以让View按我们的动画框架播放动画了。

代码实现

/**
* 组合的action可以直接交给view执行。
*/
interface Action<A : Action<A>> {
   fun add(action: A): A
   fun getAnimator(): Animator<A>
   fun startAnimation(view: View, duration: Long)
}

抽象一个Action接口,Action还可以添加Action,这里是组合模式的结构。

import android.view.View
import dora.widget.animator.AlphaAnimator
import dora.widget.animator.Animator
class AlphaAction(val alpha: Float) : Action<AlphaAction> {
   private var animator = AlphaAnimator()
   override fun add(action: AlphaAction): AlphaAction {
       animator.add(action)
       return this
   }
   override fun startAnimation(view: View, duration: Long) {
       animator.startAnimation(view, duration)
   }
   override fun getAnimator(): Animator<AlphaAction> {
       return animator
   }
   operator fun plus(action: AlphaAction) = add(action)
   init {
       animator.add(this)
   }
}

我们以透明度动画为例,在Animator中实现属性动画的逻辑,然后聚合到Action类的实现,通过代理的方式调用我们的动画实现。这里我们重写了+号操作符,这样可以支持两个对象进行相加,这个是Kotlin模仿C++的语法。

import android.view.View
import dora.widget.action.Action
import java.util.*
abstract class Animator<A : Action<A>>: Action<A> {
   protected lateinit var targetView: View
   protected var actionTree:  MutableList<A> = ArrayList()
   override fun add(action: A): A {
       actionTree.add(action)
       return actionTree[actionTree.size - 1]
   }
   override fun startAnimation(view: View, duration: Long) {
       targetView = view
   }
   override fun getAnimator(): Animator<A> {
       return this
   }
}

在Animator中,将所有的Action放到一个List集合中保存起来,当我们调用startAnimation()方法,则可以将传入的View拿到,并执行动画。

class AlphaAnimator : Animator<AlphaAction>() {
   override fun startAnimation(view: View, duration: Long) {
       super.startAnimation(view, duration)
       actionTree.add(0, AlphaAction(1.0f))
       val animator = ObjectAnimator.ofObject(
           this, ALPHA, AlphaEvaluator(),
           *actionTree.toTypedArray()
       )
       animator.duration = duration
       animator.start()
   }
   fun setAlpha(action: AlphaAction) {
       val alpha = action.alpha
       targetView.alpha = alpha
   }
   private class AlphaEvaluator : TypeEvaluator<AlphaAction> {
       override fun evaluate(
           fraction: Float,
           startValue: AlphaAction,
           endValue: AlphaAction
       ): AlphaAction {
           val action: AlphaAction
           val startAlpha = startValue.alpha
           val endAlpha = endValue.alpha
           action = if (endAlpha > startAlpha) {
               AlphaAction(startAlpha + fraction * (endAlpha - startAlpha))
           } else {
               AlphaAction(startAlpha - fraction * (startAlpha - endAlpha))
           }
           return action
       }
   }
   companion object {
       private const val ALPHA = "alpha"
   }
   override fun getAnimator(): Animator<AlphaAction> {
       return this
   }
}

比如AlphaAnimator的实现,我们这里最关键的一行代码就是使用了ObjectAnimator,用它来监听该对象属性的变化。比如这里我们监听alpha属性实际上是监听的setAlpha方法。动画变化的中间值则是通过TypeEvaluator估值器来进行计算估值的。在startAnimation()方法被调用的时候,我们默认在最前面添加了一个默认值。

actionTree.add(0, AlphaAction(1.0f))

我这里只是抛砖引玉,你可以做得更好,比如将初始状态不要写死,让子类去指定或在使用的时候动态指定,这样就会更加的灵活。

abstract class PathAction internal constructor(
   val x: Float,
   val y: Float
) : Action<PathAction> {
   private var animator = PathAnimator()
   override fun add(action: PathAction): PathAction {
       animator.add(action)
       return this
   }
   override fun startAnimation(view: View, duration: Long) {
       animator.startAnimation(view, duration)
   }
   override fun getAnimator(): Animator<PathAction> {
       return animator
   }
   operator fun plus(action: PathAction) = add(action)
   init {
       animator.add(this)
   }
}

移动的动画也是类似的逻辑,我们基于Path实现移动动画。

class PathAnimator : Animator<PathAction>() {
   private val PATH = "path"
   override fun startAnimation(view: View, duration: Long) {
       super.startAnimation(view, duration)
       actionTree.add(0, MoveTo(0f, 0f))
       val animator = ObjectAnimator.ofObject(
           this, PATH, PathEvaluator(),
           *actionTree.toTypedArray()
       )
       animator.duration = duration
       animator.start()
   }
   fun setPath(action: MoveTo) {
       val x = action.x
       val y = action.y
       targetView.translationX = x
       targetView.translationY = y
   }
   private inner class PathEvaluator : TypeEvaluator<PathAction> {
       override fun evaluate(fraction: Float, startValue: PathAction, endValue: PathAction): PathAction {
           var x = 0f
           var y = 0f
           if (endValue is MoveTo) {
               x = endValue.x
               y = endValue.y
           }
           if (endValue is LineTo) {
               x = startValue.x + fraction * (endValue.x - startValue.x)
               y = startValue.y + fraction * (endValue.y - startValue.y)
           }
           val ratio = 1 - fraction
           if (endValue is QuadTo) {
               x = Math.pow(ratio.toDouble(), 2.0)
                   .toFloat() * startValue.x + (2 * fraction * ratio
                       * (endValue).inflectionX) + (Math.pow(
                   endValue.x.toDouble(),
                   2.0
               )
                   .toFloat()
                       * Math.pow(fraction.toDouble(), 2.0).toFloat())
               y = Math.pow(ratio.toDouble(), 2.0)
                   .toFloat() * startValue.y + (2 * fraction * ratio
                       * (endValue).inflectionY) + (Math.pow(
                   endValue.y.toDouble(),
                   2.0
               )
                   .toFloat()
                       * Math.pow(fraction.toDouble(), 2.0).toFloat())
           }
           if (endValue is CubicTo) {
               x = Math.pow(ratio.toDouble(), 3.0).toFloat() * startValue.x + (3 * Math.pow(
                   ratio.toDouble(),
                   2.0
               ).toFloat() * fraction
                       * (endValue).inflectionX1) + (3 * ratio *
                       Math.pow(fraction.toDouble(), 2.0).toFloat()
                       * (endValue).inflectionX2) + Math.pow(fraction.toDouble(), 3.0)
                   .toFloat() * endValue.x
               y = Math.pow(ratio.toDouble(), 3.0).toFloat() * startValue.y + (3 * Math.pow(
                   ratio.toDouble(),
                   2.0
               ).toFloat() * fraction
                       * (endValue).inflectionY1) + (3 * ratio *
                       Math.pow(fraction.toDouble(), 2.0).toFloat()
                       * (endValue).inflectionY2) + Math.pow(fraction.toDouble(), 3.0)
                   .toFloat() * endValue.y
           }
           return MoveTo(x, y)
       }
   }
   override fun getAnimator(): Animator<PathAction> {
       return this
   }
}

曲线运动则牵扯到一些贝瑟尔曲线的知识。 比如二阶的贝瑟尔曲线

class QuadTo(val inflectionX: Float, val inflectionY: Float, x: Float, y: Float) :
   PathAction(x, y)

和三阶的贝瑟尔曲线

class CubicTo(
   val inflectionX1: Float,
   val inflectionX2: Float,
   val inflectionY1: Float,
   val inflectionY2: Float,
   x: Float,
   y: Float
) : PathAction(x, y)

直线运动则是定义了MoveTo和LineTo两个类。

class MoveTo(x: Float, y: Float) : PathAction(x, y)
class LineTo(x: Float, y: Float) : PathAction(x, y)

调用动画框架API

我们贺卡的动画就是使用了以下的写法,同一类Action可以通过+号操作符进行合并,我们可以同时调用这四类Action进行动画效果的叠加,这样可以让动画效果更加丰富。

(AlphaAction(0.2f) + AlphaAction(1f)).startAnimation(ivRabbit, 2000)
       (MoveTo(-500f, 100f)
               + LineTo(-400f, 80f)
                       + LineTo(-300f, 50f)
                       + LineTo(-200f, 100f)
                       + LineTo(-100f, 80f)
               + LineTo(0f, 100f)
               + LineTo(100f, 80f)
               + LineTo(200f, 50f)
               + LineTo(300f, 100f)
               + LineTo(400f, 80f)
               )
   .startAnimation(ivRabbit, 2000)
(RotateAction(0f) + RotateAction(180f)+ RotateAction(360f)) .startAnimation(ivRabbit, 4000)
ScaleAction(2f, 2f).startAnimation(ivRabbit, 8000)
Handler().postDelayed({
   MoveTo(0f, 0f).startAnimation(ivRabbit, 500)
}, 8000)

兴趣是最好的老师,本文篇幅有限,我们可以通过Android的代码在Android手机上实现各种各样炫酷的效果。跟着哆啦一起玩转Android自定义View吧。

来源:https://juejin.cn/post/7188660825945538619

标签:Android,新年贺卡,动画
0
投稿

猜你喜欢

  • C#实现char字符数组与字符串相互转换的方法

    2022-01-18 05:28:52
  • 采用C#实现软件自动更新的方法

    2021-12-30 19:13:38
  • SpringBoot通过源码探究静态资源的映射规则实现

    2022-03-26 19:05:53
  • Android中listview和imageview实现条目单选效果

    2022-12-05 05:02:31
  • Android中EditText光标的显示与隐藏方法

    2022-03-01 17:52:35
  • Android中的Adapter简单介绍

    2023-01-22 10:01:51
  • 一些比较实用的 Android adb 命令分享

    2023-12-22 12:39:41
  • 详解Java线程堆栈

    2021-06-29 03:37:38
  • Android 自定义View 密码框实例代码

    2022-08-22 17:06:02
  • java微信公众号支付开发之现金红包

    2023-09-01 17:28:38
  • java如何实现字符串中的字母排序

    2021-09-17 14:55:11
  • C#中的多线程多参数传递详解

    2023-08-02 22:23:46
  • Java抛出异常与自定义异常类应用示例

    2022-10-23 01:58:52
  • 基于C#实现简单的随机抽奖小程序

    2023-07-14 10:15:50
  • 深入理解strcpy与memcpy的区别

    2023-02-23 21:45:32
  • 解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

    2023-04-19 00:21:47
  • java中重载,继承,重写和多态的区别

    2022-05-06 06:40:16
  • 用C# 实现鼠标框选效果的实现代码

    2023-04-18 14:08:26
  • Jackson 反序列化时实现大小写不敏感设置

    2021-11-18 06:17:18
  • Android 下载文件通知栏显示进度条功能的实例代码

    2023-09-01 19:42:50
  • asp之家 软件编程 m.aspxhome.com