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

目录
  • 引言
  • 需要使用到的知识点
    • 思路分析
    • 代码实现
    • 调用动画框架API

引言

什么?兔了个兔?吐了还要吐?首先今天,我们自己用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吧。

以上就是Android代码实现新年贺卡动画示例详解的详细内容,更多关于Android新年贺卡动画的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Compose衰减动画Animatable使用详解

    目录 前言 animateDecay splineBasedDecay rememberSplineBasedDecay exponentialDecay 实战 最后 前言 之前介绍了 Animatable 动画以及其 animateTo和 snapTo两个开启动画 api 的使用,实际上 Animatable 除了这两个 api 以外还有一个 animateDecay即本篇要介绍的衰减动画. 什么是衰减动画呢?就是动画速度由快到慢最后停止,最常见的应用场景就是惯性动画,比如滑动列表时手指松开后

  • Android Compose 属性动画使用探索详解

    目录 前言 使用探索 ObjectAnimator 使用探索 ValueAnimator 使用探索 Compose 函数中使用属性动画 实战 上传开始动画 上传进度动画 上传完成动画 最后 前言 Jetpack Compose(简称 Compose )是 Google 官方推出的基于 Kotlin 语言的 Android 新一代 UI 开发框架,其采用声明式的 UI 编程特性使得 Android 应用界面的编写和维护变得更加简单. 本专栏将详细介绍在使用 Compose 进行 UI 开发中如何实

  • Android Flutter实现五种酷炫文字动画效果详解

    目录 前言 波浪涌动效果 波浪线跳动文字组 彩虹动效 滚动广告牌效果 打字效果 其他效果 自定义效果 总结 前言 偶然逛国外博客,看到了一个介绍文字动画的库,进入 pub 一看,立马就爱上这个动画库了,几乎你能想到的文字动画效果它都有!现在正式给大家安利一下这个库:animated_text_kit.本篇我们介绍几个酷炫的效果,其他的效果大家可以自行查看官网文档使用. 波浪涌动效果 波浪涌动 上面的动画效果只需要下面几行代码,其中loadUntil用于控制波浪最终停留的高度,取值是0-1.0,如

  • Android Activity共享元素动画示例解析

    目录 正文 TransitionManager介绍 Scene(场景) 生成场景 Transition(过渡) OverlayView和ViewGroupOverlay GhostView Activity的共享元素源码分析 我们先以ActivityA打开ActivityB为例 ActivityB返回ActivityA SharedElementCallback回调总结 正文 所谓Activity共享元素动画,就是从ActivityA跳转到ActivityB 通过控制某些元素(View)从Act

  • Android 补间动画及组合AnimationSet常用方法详解

    目录 补间动画 RotateAnimation 动画示例 ScaleAnimation 动画示例 TranslateAnimation 动画示例 AlphaAnimation 动画示例 AnimationSet 动画组合 动画示例 补间动画 Android常用的四种补间动画分别为RotateAnimation.ScaleAnimation.TranslateAnimation.AlphaAnimation,他们的父类为Animation,UML类图如下: 父类通用方法有: public void

  • Android实现缩放动画

    本文实例为大家分享了Android实现缩放动画的具体代码,供大家参考,具体内容如下 核心方法 public void startAnimation(Animation animation) 执行动画,参数可以是各种动画的对象,Animation的多态,也可以是组合动画,后面会有. 4个参数构造方法 /**  * Constructor to use when building a ScaleAnimation from code  *   * @param fromX Horizontal sc

  • Android动效Compose贝塞尔曲线动画规格详解

    目录 正文 贝塞尔曲线 解析动画曲线 曲线源码分析 总结 正文 写Compose动画的时候使用animateXAsState的时候会注意到一个参数——animationSpec,如下: val borderRadius by animateIntAsState( targetValue = if (isRound) 100 else 0, animationSpec = tween( durationMillis = 3000, easing = LinearEasing ) ) 此处就不深入探

  • Android开发Kotlin实现圆弧计步器示例详解

    目录 效果图 定义控件的样式 自定义StepView 绘制文本坐标 Android获取中线到基线距离 效果图 定义控件的样式 看完效果后,我们先定义控件的样式 <!-- 自定义View的名字 StepView --> <!-- name 属性名称 format 格式 string 文字 color 颜色 dimension 字体大小 integer 数字 reference 资源或者颜色 --> <declare-styleable name="StepView&q

  • Android车载多媒体开发MediaSession框架示例详解

    目录 一.多媒体应用架构 1.1 音视频传统应用架构 1.2 MediaSession 框架 媒体会话 媒体控制器 二.MediaSession 2.1 概述 2.2 MediaBrowser 2.2.1 MediaBrowser.ConnectionCallback 2.2.2 MediaBrowser.ItemCallback 2.2.3 MediaBrowser.MediaItem 2.2.4 MediaBrowser.SubscriptionCallback 2.3 MediaContr

  • Android 动态加载 so实现示例详解

    目录 背景 so动态加载介绍 从一个例子出发 so库检索与删除 动态加载so 结束了吗? ELF文件 扩展 总结 背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可避免的在开发中遇到各种各样需要用到native的需求,所以so库的动态化可以减少极大的包体积,自从2020腾讯的bugly团队发部关于动态化so的相关文章后,已经过去两年了,相关文章,经过两年的考验,实际上so动态加载也是非常成熟的一项技术了. 但是很遗憾,许多公司都还没有这方面的涉略又或者说不知

  • Android 应用程序的启动流程示例详解

    目录 应用进程的启动流程 1.ActivityStackSupervisor.startSpecificActivity 2.ATMS.startProcessAsync 3.LocalService.startProcess 4.startProcessLocked函数 5.ProcessList.startProcessLocked 6.ProcessList.startProcessLocked重载 7.ProcessList.startProcess 8.ZygoteState.star

  • Android画图实现MPAndroidchart折线图示例详解

    目录 效果图 依赖 activity.xml MainActivity MyMarkerView 自定义class maekertextview .xml 常用属性 效果图 用的是3.1.0的依赖 依赖 allprojects { repositories { jcenter() maven { url "https://jitpack.io" } } } //依赖 dependencies{ implementation 'com.github.PhilJay:MPAndroidCh

  • Android App 与 U 盘通信示例详解

    前言 对于 U 盘的了解,相信大多数人应该只停留在跟 U 盘跟电脑通信的阶段,其实现在通过OTG 线就可以实现手机跟 U 盘之间的数据操作,不仅可以将 U 盘中的文件读取到手机中来,还能将手机中的文件导出到 U 盘中,从而实现手机与 U 盘之间的通信.本文将从 Android App 入手,通过相关的代码,带大家一步步了解手机与 U 盘之间的通信.代码我已经放上 Github 了,有需要的点击这里 . 一.自定义广播接收器接收 U 盘相关的信息 在 U 盘插入或插出的时候,系统都会发出一条相关的

  • android教程之service使用方法示例详解

    Service的生命周期 (适用于2.1及以上) 1. 被startService的无论是否有任何活动绑定到该Service,都在后台运行.onCreate(若需要) -> onStart(int id, Bundle args).  多次startService,则onStart调用多次,但不会创建多个Service实例,只需要一次stop.该Service一直后台运行,直到stopService或者自己的stopSelf()或者资源不足由平台结束. 2. 被bindService的调用bin

  • 如何利用Android Studio将moudle变成jar示例详解

    前言 本文主要给大家介绍的是关于利用Android Studio将moudle变成jar的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 方法如下: 1.在moudle中的build.gradle文件中添加如下代码:(和android.dependencies标签同级) task makeJar(type: Copy) { delete 'build/libs/test.jar' from('build/intermediates/bundles/release/')

  • Android布局加载之LayoutInflater示例详解

    前言 Activity 在界面创建时需要将 XML 布局文件中的内容加载进来,正如我们在 ListView 或者 RecyclerView 中需要将 Item 的布局加载进来一样,都是使用 LayoutInflater 来进行操作的. LayoutInflater 实例的获取有多种方式,但最终是通过(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来得到的,也就是说加载布局的 LayoutInflat

  • C# Winform中如何绘制动画示例详解

    前言 这里介绍一个.net自身携带的类ImageAnimator,这个类类似于控制动画的时间轴,使用ImageAnimator.CanAnimate可以判断一个图片是否为动画,调用ImageAnimator.Animate可以开始播放动画,即每经过一帧的时间触发一次OnFrameChanged委托,我们只要在该委托中将Image的活动帧选至下一帧再迫使界面重绘就可以实现动画效果了. 为了方便以后的使用,我将这些代码整合到了一起,形成一个AnimateImage类,该类提供了CanAnimate.

随机推荐