Android代码实现新年贺卡动画示例详解
作者:dora 发布时间:2022-09-11 00:28:49
引言
什么?兔了个兔?吐了还要吐?首先今天,我们自己用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


猜你喜欢
- C语言求三次方根前话说到C语言求根,我们一般会想到用sqrt(x)函数,它的输入值和返回值都是double型,x取整将会导致编译器错误。但是
- 这是一个运用网格布局来做的简易计算器,可能没有那么美观,大家可以继续完善首先先看看成果吧首先先建一个新的Project Calculator
- 在Java项目开发中,Maven是我们最常用的依赖管理和构建工具了!我们常常通过添加dependency节点,就能够很方便地加入依赖,而不需
- 1、动态SQL片段通过SQL片段达到代码复用 <!-- 动态条件分页查询 --> <sql i
- 装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的一个过程。 当 CLR 对值类型进行装箱时,会将该值包装到 Syst
- 本文实例讲述了Java枚举类用法。分享给大家供大家参考。具体如下:package com.school.stereotype; /** *
- 这篇文章主要介绍了java加载property文件配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无
- 这篇文章主要介绍了Springboot如何设置静态资源缓存一年,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 数据校验在web应用里是非常重要的功能,尤其是在表单输入中。在这里采用Hibernate-Vapdator进行校验,该方法实现了JSR-30
- 最近我尝试使用ViewPager+GridView实现的,看起来一切正常,废话不多说,具体代码如下:如图是效果图 首先分析下思路1
- 本文实例讲述了Android编程实现小说阅读器滑动效果的方法。分享给大家供大家参考,具体如下:看过小说都知道小说阅读器翻页有好多种效果,比如
- 写在前面jenkins作为java的好 * ,经历过单体项目时代->集群项目时代->容器集群分布式时代,使用稳定可靠,cpu友好(
- [LeetCode] 159. Longest Substring with At Most Two Distinct Characters
- 一、使用无参构造方法创建二、使用静态工厂创建三、使用实例工厂创建来源:https://www.cnblogs.com/jock766/p/1
- FeignClient重试机制造成的接口幂等性Feign源码分析,其实现类在 SynchronousMethodHandler,实现方法是p
- 前言Spring Boot中在yaml中编写的自定义变量、数组、对象等,在代码中读取该yaml配置文件中内容的三种方式。实现在代码中运用配置
- 几个月前写过一篇博客《xUtils3.0框架学习笔记》 ,上面也有记录通过xUtils实现文件上传的使用方法,代码如下:private vo
- @EventListener 异步中使用condition的问题@EventListener是spring在4.2+推出的更好的使用spri
- 本文介绍了java 读写Parquet格式的数据,分享给大家,具体如下:import java.io.BufferedReader;impo