首頁 > 軟體

Android程式碼實現新年賀卡動畫範例詳解

2023-01-16 14:02:54

引言

什麼?兔了個兔?吐了還要吐?首先今天,我們自己用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新年賀卡動畫的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com