Android代碼實(shí)現(xiàn)新年賀卡動(dòng)畫(huà)示例詳解
引言
什么?兔了個(gè)兔?吐了還要吐?首先今天,我們自己用android程序?qū)崿F(xiàn)一個(gè)兔年的新年賀卡。下面就是見(jiàn)證美好的時(shí)刻,上效果。
好,我們來(lái)使用Android動(dòng)畫(huà)的知識(shí),來(lái)實(shí)現(xiàn)這樣一個(gè)動(dòng)畫(huà)效果吧。
需要使用到的知識(shí)點(diǎn)
架構(gòu)設(shè)計(jì)、Android視圖動(dòng)畫(huà)、TypeEvaluator、Path、組合模式、代理模式。
思路分析
我們回顧動(dòng)畫(huà)的種類(lèi),補(bǔ)間動(dòng)畫(huà)、幀動(dòng)畫(huà)、屬性動(dòng)畫(huà)以及Android View自帶的視圖動(dòng)畫(huà)。我們今天自己基于屬性動(dòng)畫(huà)來(lái)打造一個(gè)山寨版的Android視圖動(dòng)畫(huà)吧。我們可以從平移動(dòng)畫(huà)、縮放動(dòng)畫(huà)、旋轉(zhuǎn)動(dòng)畫(huà)和透明度動(dòng)畫(huà)中抽象出一個(gè)基類(lèi)Action類(lèi)。我是不會(huì)告訴你這個(gè)類(lèi)的命名我是抄的cocos2d的。然后我們擴(kuò)展Action類(lèi),實(shí)現(xiàn)這四種動(dòng)畫(huà),再作用在View上。這樣就可以讓View按我們的動(dòng)畫(huà)框架播放動(dòng)畫(huà)了。
代碼實(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)畫(huà)為例,在Animator中實(shí)現(xiàn)屬性動(dòng)畫(huà)的邏輯,然后聚合到Action類(lèi)的實(shí)現(xiàn),通過(guò)代理的方式調(diào)用我們的動(dòng)畫(huà)實(shí)現(xiàn)。這里我們重寫(xiě)了+號(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)畫(huà)。
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)畫(huà)變化的中間值則是通過(guò)TypeEvaluator估值器來(lái)進(jìn)行計(jì)算估值的。在startAnimation()方法被調(diào)用的時(shí)候,我們默認(rèn)在最前面添加了一個(gè)默認(rèn)值。
actionTree.add(0, AlphaAction(1.0f))
我這里只是拋磚引玉,你可以做得更好,比如將初始狀態(tài)不要寫(xiě)死,讓子類(lè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)畫(huà)也是類(lèi)似的邏輯,我們基于Path實(shí)現(xiàn)移動(dòng)動(dòng)畫(huà)。
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è)類(lèi)。
class MoveTo(x: Float, y: Float) : PathAction(x, y)
class LineTo(x: Float, y: Float) : PathAction(x, y)
調(diào)用動(dòng)畫(huà)框架API
我們賀卡的動(dòng)畫(huà)就是使用了以下的寫(xiě)法,同一類(lèi)Action可以通過(guò)+號(hào)操作符進(jìn)行合并,我們可以同時(shí)調(diào)用這四類(lèi)Action進(jìn)行動(dòng)畫(huà)效果的疊加,這樣可以讓動(dòng)畫(huà)效果更加豐富。
(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)畫(huà)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android新年賀卡動(dòng)畫(huà)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android Compose衰減動(dòng)畫(huà)Animatable使用詳解
- Android動(dòng)效Compose貝塞爾曲線動(dòng)畫(huà)規(guī)格詳解
- Android?補(bǔ)間動(dòng)畫(huà)及組合AnimationSet常用方法詳解
- Android Compose 屬性動(dòng)畫(huà)使用探索詳解
- Android?Activity共享元素動(dòng)畫(huà)示例解析
- Android實(shí)現(xiàn)縮放動(dòng)畫(huà)
- Android Flutter實(shí)現(xiàn)五種酷炫文字動(dòng)畫(huà)效果詳解
相關(guān)文章
學(xué)習(xí)Android自定義Spinner適配器
這篇文章主要為大家詳細(xì)介紹了學(xué)習(xí)Android自定義Spinner適配器的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05Android加載對(duì)話框同時(shí)異步執(zhí)行實(shí)現(xiàn)方法
Android中通過(guò)子線程連接網(wǎng)絡(luò)獲取資料,同時(shí)顯示加載進(jìn)度對(duì)話框給用戶的操作2012-11-11神經(jīng)網(wǎng)絡(luò)API、Kotlin支持,那些你必須知道的Android 8.1預(yù)覽版和Android Studio 3.0新特
這篇文章主要介紹了神經(jīng)網(wǎng)絡(luò)API、Kotlin支持,那些你必須了解的Android 8.1預(yù)覽版和Android Studio 3.0新特性,需要的朋友可以參考下2017-10-10Android設(shè)備間實(shí)現(xiàn)藍(lán)牙(Bluetooth)共享上網(wǎng)
這篇文章主要為大家詳細(xì)介紹了Android設(shè)備間實(shí)現(xiàn)藍(lán)牙(Bluetooth)共享上網(wǎng)的方法,主要以圖片的方式向大家展示藍(lán)牙共享上網(wǎng)2016-03-03Android實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)的全過(guò)程記錄
對(duì)于android軟件開(kāi)發(fā)初級(jí)學(xué)習(xí)者來(lái)說(shuō),簡(jiǎn)單的頁(yè)面跳轉(zhuǎn)是必學(xué)的,這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10Android 進(jìn)入設(shè)備后臺(tái)data文件夾的辦法
Android 進(jìn)入設(shè)備后臺(tái)data文件夾的辦法,需要的朋友可以參考一下2013-05-05Android使用第三方庫(kù)實(shí)現(xiàn)日期選擇器
這篇文章主要為大家詳細(xì)介紹了Android使用第三方庫(kù)實(shí)現(xiàn)日期選擇器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10Android 使用<layer-list>實(shí)現(xiàn)微信聊天輸入框功能
<layer-list> 標(biāo)簽可以設(shè)置LayerDrawable,一種有層次的Drawable疊加效果,<layer-list> 可以包含多個(gè) <item>標(biāo)簽。這篇文章主要介紹了Android 使用<layer-list>實(shí)現(xiàn)微信聊天輸入框,需要的朋友可以參考下2017-05-05