Android代码实现新年贺卡动画

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

20230115_070418_edit.gif

好,我们来使用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]

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值