Android代碼實(shí)現(xiàn)新年賀卡動(dòng)畫示例詳解
引言
什么?兔了個(gè)兔?吐了還要吐?首先今天,我們自己用android程序?qū)崿F(xiàn)一個(gè)兔年的新年賀卡。下面就是見(jiàn)證美好的時(shí)刻,上效果。

好,我們來(lái)使用Android動(dòng)畫的知識(shí),來(lái)實(shí)現(xiàn)這樣一個(gè)動(dòng)畫效果吧。
需要使用到的知識(shí)點(diǎn)
架構(gòu)設(shè)計(jì)、Android視圖動(dòng)畫、TypeEvaluator、Path、組合模式、代理模式。
思路分析
我們回顧動(dòng)畫的種類,補(bǔ)間動(dòng)畫、幀動(dòng)畫、屬性動(dòng)畫以及Android View自帶的視圖動(dòng)畫。我們今天自己基于屬性動(dòng)畫來(lái)打造一個(gè)山寨版的Android視圖動(dòng)畫吧。我們可以從平移動(dòng)畫、縮放動(dòng)畫、旋轉(zhuǎn)動(dòng)畫和透明度動(dòng)畫中抽象出一個(gè)基類Action類。我是不會(huì)告訴你這個(gè)類的命名我是抄的cocos2d的。然后我們擴(kuò)展Action類,實(shí)現(xiàn)這四種動(dòng)畫,再作用在View上。這樣就可以讓View按我們的動(dòng)畫框架播放動(dòng)畫了。
代碼實(shí)現(xiàn)
/**
* 組合的action可以直接交給view執(zhí)行。
*/
interface Action<A : Action<A>> {
fun add(action: A): A
fun getAnimator(): Animator<A>
fun startAnimation(view: View, duration: Long)
}
抽象一個(gè)Action接口,Action還可以添加Action,這里是組合模式的結(jié)構(gòu)。
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)
}
}
我們以透明度動(dòng)畫為例,在Animator中實(shí)現(xiàn)屬性動(dòng)畫的邏輯,然后聚合到Action類的實(shí)現(xiàn),通過(guò)代理的方式調(diào)用我們的動(dòng)畫實(shí)現(xiàn)。這里我們重寫了+號(hào)操作符,這樣可以支持兩個(gè)對(duì)象進(jìn)行相加,這個(gè)是Kotlin模仿C++的語(yǔ)法。
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放到一個(gè)List集合中保存起來(lái),當(dāng)我們調(diào)用startAnimation()方法,則可以將傳入的View拿到,并執(zhí)行動(dòng)畫。
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的實(shí)現(xiàn),我們這里最關(guān)鍵的一行代碼就是使用了ObjectAnimator,用它來(lái)監(jiān)聽(tīng)該對(duì)象屬性的變化。比如這里我們監(jiān)聽(tīng)alpha屬性實(shí)際上是監(jiān)聽(tīng)的setAlpha方法。動(dòng)畫變化的中間值則是通過(guò)TypeEvaluator估值器來(lái)進(jìn)行計(jì)算估值的。在startAnimation()方法被調(diào)用的時(shí)候,我們默認(rèn)在最前面添加了一個(gè)默認(rèn)值。
actionTree.add(0, AlphaAction(1.0f))
我這里只是拋磚引玉,你可以做得更好,比如將初始狀態(tài)不要寫死,讓子類去指定或在使用的時(shí)候動(dòng)態(tài)指定,這樣就會(huì)更加的靈活。
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)
}
}
移動(dòng)的動(dòng)畫也是類似的邏輯,我們基于Path實(shí)現(xiàn)移動(dòng)動(dòng)畫。
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
}
}
曲線運(yùn)動(dòng)則牽扯到一些貝瑟爾曲線的知識(shí)。 比如二階的貝瑟爾曲線
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)
直線運(yùn)動(dòng)則是定義了MoveTo和LineTo兩個(gè)類。
class MoveTo(x: Float, y: Float) : PathAction(x, y)
class LineTo(x: Float, y: Float) : PathAction(x, y)
調(diào)用動(dòng)畫框架API
我們賀卡的動(dòng)畫就是使用了以下的寫法,同一類Action可以通過(guò)+號(hào)操作符進(jìn)行合并,我們可以同時(shí)調(diào)用這四類Action進(jìn)行動(dòng)畫效果的疊加,這樣可以讓動(dòng)畫效果更加豐富。
(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)
興趣是最好的老師,本文篇幅有限,我們可以通過(guò)Android的代碼在Android手機(jī)上實(shí)現(xiàn)各種各樣炫酷的效果。跟著哆啦一起玩轉(zhuǎn)Android自定義View吧。
以上就是Android代碼實(shí)現(xiàn)新年賀卡動(dòng)畫示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android新年賀卡動(dòng)畫的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 擴(kuò)大 View 的點(diǎn)擊區(qū)域的方法
這篇文章主要介紹了Android 擴(kuò)大 View 的點(diǎn)擊區(qū)域的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
JetPack Compose底部導(dǎo)航欄的實(shí)現(xiàn)方法詳解
開發(fā)一個(gè)新項(xiàng)目,底部導(dǎo)航欄一般是首頁(yè)的標(biāo)配,在以前的xml布局中,我們可以很輕松的是用谷歌提供的BottomNavigationView或者自定義來(lái)實(shí)現(xiàn)底部導(dǎo)航的功能,在Compose中也有也提供了一個(gè)類似的控件androidx.compose.material.BottomNavigation2022-09-09
Android使用原生組件WebView加載網(wǎng)頁(yè)和數(shù)據(jù)的方法
這篇文章主要介紹了Android使用原生組件WebView加載網(wǎng)頁(yè)和數(shù)據(jù)的方法的相關(guān)資料,需要的朋友可以參考下2016-09-09
Android學(xué)習(xí)教程之分類側(cè)滑菜單(5)
這篇文章主要為大家詳細(xì)介紹了Android學(xué)習(xí)教程之分類側(cè)滑菜單的具體代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android中使用Notification實(shí)現(xiàn)狀態(tài)欄的通知
本文主要介紹了android利用Notification實(shí)現(xiàn)狀態(tài)欄的通知的示例代碼。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Kotlin如何優(yōu)雅地判斷EditText數(shù)據(jù)是否為空詳解
這篇文章主要給大家介紹了關(guān)于Kotlin如何優(yōu)雅地判斷EditText數(shù)據(jù)是否為空的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用kotlin具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Android Activity的啟動(dòng)過(guò)程源碼解析
這篇文章主要介紹了Android Activity的啟動(dòng)過(guò)程源碼解析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

