Android動(dòng)畫:兩大必學(xué)動(dòng)畫詳解之屬性動(dòng)畫(Property Animation)

引言

????屬性動(dòng)畫是Android 3.0(API 11)加入的,如果想在之前的版本使用屬性動(dòng)畫,建議使用nineoldandroids,網(wǎng)址是:http://nineoldandroids.com

? ? 屬性動(dòng)畫的加入產(chǎn)生了一個(gè)新的問(wèn)題,為什么要加入屬性動(dòng)畫呢?

? ? View動(dòng)畫雖然簡(jiǎn)單,但也存在著一些問(wèn)題:

? ? ①只能作用于View對(duì)象。

? ??②不能改變真是屬性值,而只是改變了視覺效果。

? ??③只有四種動(dòng)畫效果,效果種類少。

????而屬性動(dòng)畫(Property Animation)是一個(gè)強(qiáng)大的動(dòng)畫框架,允許作用在任意對(duì)象上。可以隨著時(shí)間的變化改變?nèi)魏螌?duì)象的屬性,不管這個(gè)對(duì)象是否繪制在屏幕上,然后通過(guò)對(duì)屬性的應(yīng)用而達(dá)到動(dòng)畫的效果。

1 ValueAnimator

? ??ValueAnimator是屬性動(dòng)畫重要的類,ObjectAnimator也是繼承自ValueAnimator,ValueAnimator有五個(gè)常用的方法。

圖1-1?ValueAnimator方法

1.1 ofFloat、ofInt、OfArgb

? ? ofFloat構(gòu)造并返回一個(gè)在float值之間動(dòng)畫的ValueAnimator。傳入一個(gè)參數(shù)時(shí)這個(gè)參數(shù)就是目標(biāo)值,但是只有一個(gè)參數(shù)的情況很少用,因?yàn)闊o(wú)法確定起始值是什么。所以通常情況下,需要傳入兩個(gè)或者以上的參數(shù)。先看看使用方法,如下代碼所示。

? ??var anim: ValueAnimator = ValueAnimator.ofFloat(1f,0f,1f)

????anim.duration =3000

????anim.start()

? ? 方法很簡(jiǎn)單,動(dòng)畫開始之后可以使用addUpdateListener方法添加AnimatorUpdateListener對(duì)動(dòng)畫進(jìn)行監(jiān)聽。addUpdateListener方法是很重要的方法,因?yàn)閂alueAnimator主要是對(duì)值進(jìn)行操作的動(dòng)畫,因此需要通過(guò)監(jiān)聽去獲取相應(yīng)的值來(lái)進(jìn)行操作以完成動(dòng)畫效果。

anim.addUpdateListener{

? ? var value =it.animatedValue as Float

????Log.d("MainActivity", value.toString())

}

? ? 日志比較長(zhǎng)我就不做截圖或者復(fù)制了。從日志上可以看出來(lái),值得變化是從1.0f開始然過(guò)渡到0.0f再過(guò)渡到1.0f。因?yàn)闆](méi)有設(shè)置插值器,因此使用的是默認(rèn)插值器AccelerateDecelerateInterpolator,這個(gè)插值器的效果是開始和結(jié)束慢中間加速。將value應(yīng)用在Alpha上可以呈現(xiàn)如下圖1-2效果。

private fun myOfFloat() {

????var anim: ValueAnimator = ValueAnimator.ofFloat(1.0f,0.0f,1.0f)

????anim.duration = 3000

????tv.setOnClickListener{

????????anim.start()

????}

????anim.addUpdateListener{

????????var value =it.animatedValue as Float

????????Log.d("MainActivity", value.toString())

????????tv.alpha = value

????}

}

圖1-2 ofFloat應(yīng)用效果圖

? ? 在某些情況下也許你并不需要float類型,而只是需要使用Int類型,就可以使用ofInt方法。先看看使用方法,代碼如下所示:

var anim:ValueAnimator = ValueAnimator.ofInt(0,1)

anim.duration =1000

anim.start()

????ofArgb構(gòu)造并返回一個(gè)在顏色值之間變化的ValueAnimator。ofArgb其實(shí)和ofInt本質(zhì)上是一樣的,可以從下圖1-3、1-4源碼可看出來(lái),ofArgb只是設(shè)置了估值器(Evaluator),?ArgbEvaluator是一個(gè)估值器,它決定了ofArgb傳入的值是如何變化的。

圖1-3 ofInt源碼
圖1-4 ofArgb源碼

? ? 使用ofArgb的方式也很簡(jiǎn)單,例如下面方法,傳入兩個(gè)32位顏色值即可達(dá)到圖1-5效果。

private fun myOfArgb() {

????var startColor = resources.getColor(R.color.colorAccent)

????var enColor = resources.getColor(R.color.colorPrimary)

????var anim: ValueAnimator = ValueAnimator.ofArgb(startColor,enColor)

????anim.duration =3000

? ? tv.setOnClickListener{

? ? ? ? anim.start()

????}

? ? anim.addUpdateListener{

? ? ? ? var color: Int =it.animatedValue as Int

????????tv.setBackgroundColor(color)

????}

}

圖1-5 ofArgb效果

1.2 ofObject

????ofObject構(gòu)造并返回一個(gè)在Object對(duì)象之間變化的ValueAnimator。通過(guò)下面圖1-6源碼可以看出來(lái),ofObject至少需要傳兩個(gè)參數(shù)。

圖1-6?ofObject源碼

????evaluator 傳入的是 null,Object只能傳入兩種類型,一種是float,另一種是int類型,傳入其他的類型會(huì)把報(bào)錯(cuò)。可以在動(dòng)畫開始時(shí)初始化中看到相關(guān)的操作,源碼如下:

圖1-6 動(dòng)畫初始化操作源碼

? ? 再往下查看源碼,可以看出來(lái)sIntEvaluator是一個(gè)IntEvaluator,sFloatEvaluator是一個(gè)FloatEvaluator。

圖1-7?sIntEvaluator、sFloatEvaluator ?實(shí)例化

????如果evaluator 傳入的是 null,Object傳入的是float類型,則可以把ofObject當(dāng)成ofFloat使用,如下所示:

var anim: ValueAnimator = ValueAnimator.ofObject(null,0.0f,1.0f)

????如果TypeEvaluator傳入的是ArgbEvaluator,Object傳入的是32位顏色值,則可以把ofObject當(dāng)成ofArgb使用,如下所示:

var startColor =resources.getColor(R.color.colorAccent)

var enColor =resources.getColor(R.color.colorPrimary)

var anim: ValueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), startColor, enColor)

? ??ofObject的用處不止于此,它強(qiáng)大的地方在于可以自定義TypeEvaluator,然后傳入任意對(duì)象。本文將下一小節(jié)進(jìn)行講解。

1.3?TypeEvaluator

? ? 看了這么多關(guān)于TypeEvaluator的應(yīng)用,肯定會(huì)很好奇TypeEvaluator到底是個(gè)什么東西。TypeEvaluator其實(shí)是一個(gè)接口,只有evaluate一個(gè)方法。

圖1-8 TypeEvaluator

? ? 以IntEvaluator為例子,可以看出來(lái)IntEvaluator實(shí)現(xiàn)了TypeEvaluator。

圖1-9?IntEvaluator源碼

? ? 從上面IntEvaluator的源碼可以看出,evaluate方法對(duì)值做了處理,其中fraction是變化的百分比,然后從startValue開始按照線性速率向endValue變化。因此,如果想要自定義TypeEvaluator ,只需要在evaluate方法上下功夫就可以了。

1.4 ofPropertyValuesHolder

? ? ofPropertyValuesHolder方法構(gòu)造并返回一個(gè)指定在PropertyValuesHolder內(nèi)的值進(jìn)行動(dòng)畫的ValueAnimator。通過(guò)下面的源碼可以看出來(lái),ofPropertyValuesHolder需要傳入至少一個(gè)PropertyValuesHolder。

圖1-10?ofPropertyValuesHolder源碼

? ? 那PropertyValuesHolder又是什么呢?PropertyValuesHolder類保存有關(guān)屬性的信息以及該屬性在動(dòng)畫中應(yīng)該呈現(xiàn)的值。PropertyValuesHolder對(duì)象可用于使用ValueAnimator或ObjectAnimator創(chuàng)建并行操作多個(gè)不同屬性的動(dòng)畫。

? ??PropertyValuesHolder的方法有很多,圖1-11的方法是最重要的。

圖1-11 PropertyValuesHolder方法

? ? 首先看ofFloat的使用方法,下面的代碼先實(shí)例化了兩個(gè)PropertyValuesHolder對(duì)象,然后再把這兩個(gè)對(duì)象傳入ofPropertyValuesHolder生成一個(gè)ValueAnimator。

private fun startOfPropertyValuesHolder() {

???val valuesHolderX = PropertyValuesHolder.ofFloat("getX",0.0f,10.0f)

????val valuesHolderY = PropertyValuesHolder.ofFloat("getY",0.0f,100.0f)

????val anim = ValueAnimator.ofPropertyValuesHolder(valuesHolderX,valuesHolderY)

????anim.duration =3000

? ? anim.addUpdateListener{

? ? ? ? var getX = it.getAnimatedValue("getX") as Float

????}

? ? anim.start()

}

? ? 上面的代碼中,查看源碼發(fā)現(xiàn)通過(guò)getAnimatedValue(String propertyName)方法能獲取指定的propertyName相對(duì)應(yīng)的PropertyValuesHolder的值。還可以通過(guò)查看源碼知道,使用getAnimatedValue()只能獲取第一個(gè)PropertyValuesHolder的值。

圖1-12?getAnimatedValue(String propertyName) 源碼
圖1-13?getAnimatedValue()源碼

? ? 此時(shí)可能會(huì)想到,之前使用的ofFloat不是也可以傳遞多值嗎?它又是怎么操作的?

????其實(shí)查看ofFloat源碼,如下圖1-14就可以發(fā)現(xiàn),ofFloat傳遞多值也是實(shí)例化了一個(gè)PropertyValuesHolder對(duì)象。因?yàn)闆](méi)有產(chǎn)生多個(gè)PropertyValuesHolder對(duì)象,所以通過(guò)getAnimatedValue()方法獲取到的只是一個(gè)PropertyValuesHolder對(duì)象的值。

1-14 ofFloat源碼

? ? 再來(lái)看看ofKeyframe方法,個(gè)人認(rèn)為ofKeyframe方法是PropertyValuesHolder最重要的方法。ofKeyframe能更簡(jiǎn)單的實(shí)現(xiàn)動(dòng)畫,而不需要使用自定義TypeEvaluator。

? ? Keyframe這個(gè)類包含一個(gè)動(dòng)畫的 time/value對(duì),ValueAnimator使用Keyframe定義動(dòng)畫目標(biāo)在整個(gè)動(dòng)畫過(guò)程中所要用的值,ValueAnimator在當(dāng)前Keyframe的值和下一個(gè)Keyframe的值之間動(dòng)畫。

? ? 先看使用ofKeyframe方法的效果圖,如1-15所示。

圖1-15?Keyframe使用效果圖

? ? 再看看具體代碼是怎么寫的,代碼如下所示:

private fun startKeyFrame() {

????var progressBar2 = findViewById(R.id.progressBar3)

????var key1 = Keyframe.ofInt(0.0f, 0)

????var key2 = Keyframe.ofInt(0.5f, 100)

????var key3 = Keyframe.ofInt(1f, 80)

????var propertyValuesHolder = PropertyValuesHolder.ofKeyframe("progress", key1, key2, key3)

????var anim = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder)

????anim.duration =3000

? ? var textView = findViewById(R.id.textView)

????textView.setOnClickListener{

? ? ? ? anim.start()

????}

? ? anim.addUpdateListener{

? ? ? ? var value =it.animatedValue as Int

????????progressBar2.progress = value

????????textView.text = value.toString().plus("%")

????}

}

? ? 可以看見,首先使用Keyframe.ofInt獲取Keyframe對(duì)象,傳入兩個(gè)參數(shù),第一個(gè)參數(shù)是動(dòng)畫的百分比,第二個(gè)參數(shù)是相對(duì)應(yīng)的數(shù)據(jù)值,接著就是創(chuàng)建PropertyValuesHolder傳入OfPropertyValuesHolder方法中,最后可以通過(guò)addUpdateListener方法獲取動(dòng)畫數(shù)值對(duì)progressBar進(jìn)行操作完成動(dòng)畫效果。

? ? 其它方法我就不做過(guò)多的說(shuō)明了,因?yàn)槲蚁氩坏接惺裁磳?shí)例,哈哈哈哈~。但有幾個(gè)參數(shù)還是要了解:

? ??TypeConverter<T, V>:該類是一個(gè)抽象類,可將類型T轉(zhuǎn)換成類型V,當(dāng)動(dòng)畫中的屬性類型和值類型不相同時(shí)這是必要的,它的核心方法是convert。

? ? Property:該類是一個(gè)抽象類,用于表示宿主對(duì)象的可變值。

????Path:傳入Path表示沿著path動(dòng)畫。

2 ObjectAnimator

? ? ObjectAnimator繼承自ValueAnimator,支持目標(biāo)對(duì)象上的屬性進(jìn)行動(dòng)畫。該類的構(gòu)造函數(shù)使用參數(shù)定義將要?jiǎng)赢嫷哪繕?biāo)對(duì)象以及動(dòng)畫的屬性名稱,然后在內(nèi)部選取合適的set/get方法,然后根據(jù)需要調(diào)用動(dòng)畫的屬性。

????在代碼中實(shí)現(xiàn)ObjectAnimator,代碼如下所示,效果圖如圖2-1:

var anim = ObjectAnimator.ofFloat(textView, "Alpha", 1.0f, 0.0f, 1.0f)

anim.duration =1500

textView.setOnClickListener{

? ? anim.start()

}

圖2-1 alpha效果圖

????上面代碼中,使用ofFloat構(gòu)造ObjectAnimator,第一個(gè)參數(shù)就是目標(biāo)對(duì)象,第二個(gè)參數(shù)是屬性名,第三、四、五參數(shù)是具體動(dòng)畫數(shù)值。動(dòng)畫是如何實(shí)現(xiàn)的呢?是找到textView中的alpha屬性進(jìn)行賦值使用嗎?但其實(shí)textview沒(méi)有alpha屬性、view也沒(méi)有alpha屬性。

? ?實(shí)際上alpha這個(gè)屬性名用于反射,通過(guò)屬性名獲取setAlpha方法來(lái)使用,查看源碼發(fā)現(xiàn)TextView中沒(méi)有setAlpha方法,但是View中有setAlpha方法,因此實(shí)際上是調(diào)用了View的setAlpha方法

????同理,得到下面的例子:

var anim = ObjectAnimator.ofFloat(textView, "TranslationX", 0f,200f) //對(duì)象x軸從0移動(dòng)到200

var anim = ObjectAnimator.ofFloat(textView, "ScaleX", 1.5f)//對(duì)象x軸方法1.5倍

var anim = ObjectAnimator.ofFloat(textView, "Rotation", 0f,360f)//對(duì)象旋轉(zhuǎn)360度

? ? 我們也可以在自定義View中使用這種方法,但是需要注意set方法后的第一個(gè)字符必須是大寫的(駝峰命名法)。下面是自定義view的源代碼,在onDraw中畫了一條線,并提供了setPosition方法改變線的X軸。

class LineView : View {

????private var mPosition =0f

? ? constructor(context: Context?) :this(context, null)

????constructor(context: Context?, attrs: AttributeSet?) :this(context, attrs, 0)

????constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :super(context, attrs, defStyleAttr)

????override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

????????super.onMeasure(widthMeasureSpec, heightMeasureSpec)

????}

????override fun onDraw(canvas: Canvas) {

????????var paint= Paint()

????????paint.strokeWidth =20f

? ? ? ? paint.color =resources.getColor(R.color.colorAccent,null)

????????canvas.drawLine(0f, 0f, mPosition, 0f, paint)

????}

????fun setPosition(position: Float) {

????????mPosition = position

????????invalidate()

????}

}

? ? 接著實(shí)例化ObjectAnimator對(duì)象,可以看見ofFloat使用了“Position”作為屬性名,并且從X軸從0f過(guò)渡到1000f。效果圖如圖2-2所示:

var lineView = findViewById(R.id.lineView)

var anim = ObjectAnimator.ofFloat(lineView, "Position", 0f, 1000f)

anim.duration =1000

anim.start()

圖2-2 LineView效果圖

? ? 再來(lái)看看get方法,在LineView中加入get方法,如下所示:

fun getPosition(): Float {

return 500f

}

????get方法是用來(lái)獲取初始值用的,當(dāng)傳入的參數(shù)是一個(gè)參數(shù)的時(shí)候就需要使用到get方法獲取初始值。

var anim = ObjectAnimator.ofFloat(lineView, "Position", 1000f)

? ? 動(dòng)畫首先從500開始過(guò)渡到1000,并不是從0開始過(guò)渡到1000。看看效果圖,如下圖2-3所示:

圖2-3 LineView使用get方法效果圖

? ? 如果不提供get方法程序會(huì)報(bào)錯(cuò)嗎?程序該從哪開始?答案是如果不提供get方法,程序會(huì)出現(xiàn)如下提示,但是程序仍然能正常運(yùn)行,并且從初始值0開始過(guò)渡到1000,如圖2-2所示。

2018-10-26 11:37:12.429 11681-11681/com.zhj.ktdemo W/PropertyValuesHolder: Method getPosition() with type null not found on target class class com.zhj.ktdemo.LineView

3?AnimatorSet

????AnimatorSet是一個(gè)按照順序執(zhí)行Animator對(duì)象的類。可以一起播放動(dòng)畫,也可以按順序播放,還可以延時(shí)播放。

? ? 有兩種不同的方法可以添加動(dòng)畫到AnimatorSet,可以使用playTogether或者playSequentially方法一次添加一組動(dòng)畫,也可以將play方法與Builder類中的方法結(jié)合,逐個(gè)添加動(dòng)畫。

? ? 使用Animation.play(Aniamtor)返回一個(gè)Builder的實(shí)例,也可以直接使用Animation.Builder(Animator),他們是一樣的。接著就可以使用Builder下的方法配合傳入的Animator使用。

????Builder類有四個(gè)重要的方法,如下圖3-1所示:

圖3-1 Builder類

after(Animator):將當(dāng)前的動(dòng)畫插入到傳入的動(dòng)畫之后執(zhí)行

????例如:animatorSet.play(anim).after(anim2).after(anim3),anima2在anim3之后執(zhí)行,anim在anim2之后執(zhí)行

after(long):將動(dòng)畫延遲執(zhí)行

? ? 例如:animatorSet.play(anim).after(3000),anim延時(shí)3000毫秒之后執(zhí)行

before(Animator):將之前的動(dòng)畫插入到傳入的動(dòng)畫之前執(zhí)行

? ? 例如:animatorSet.play(anim).before(anim2).before(anim3),anim在anim2之前執(zhí)行,anim2在anim3之前執(zhí)行

with(Animator):將當(dāng)前動(dòng)畫和傳入的動(dòng)畫同時(shí)執(zhí)行

? ??例如:animatorSet.with(anim).with(anim2).with(anim3),三個(gè)動(dòng)畫同時(shí)執(zhí)行?

? ? 可以看具體的使用,代碼如下所示:

var textView = findViewById(R.id.textView)

var anim = ObjectAnimator.ofFloat(textView, "TranslationX", -500f, 0f)

anim.duration =1000

var anim2 = ObjectAnimator.ofFloat(textView, "Alpha", 0f, 1f)

anim2.duration =1000

var anim3 = ObjectAnimator.ofFloat(textView, "ScaleX", 1f, 2f)

anim3.duration =2000

var anim4 = ObjectAnimator.ofFloat(textView, "ScaleY", 1f, 2f)

anim3.duration =2000

var animatorSet = AnimatorSet()

animatorSet.play(anim).with(anim2).after(anim3).after(anim4)

textView.setOnClickListener{

? ? animatorSet.start()

}

? ? 從效果圖3-2可以看出來(lái),after(anim4)執(zhí)行在play(anim).with(anim2).after(anim3)之前,after(anim3)執(zhí)行在play(anim).with(anim2)之前,play(anim).with(anim2)同時(shí)執(zhí)行。

圖3-2 AnimatorSet效果圖

? ? 如果有需要,還可以繼續(xù)使用addListener添加動(dòng)畫監(jiān)聽,代碼如下所示:

animatorSet.addListener(object :Animator.AnimatorListener{

????override fun onAnimationRepeat(animation: Animator?) {

????}

????override fun onAnimationEnd(animation: Animator?) {

????}

????override fun onAnimationCancel(animation: Animator?) {?

????}

????override fun onAnimationStart(animation: Animator?) {

????}

}

4 總結(jié)

? ? ValueAnimator繼承自Animator,主要是對(duì)屬性值進(jìn)行操作。配合自定義估值器TypeEvaluator,可以自定義屬性值進(jìn)行動(dòng)畫。

? ? ObjectAnimator繼承自ValueAnimator,可以直接操作對(duì)象,只要對(duì)象中有合適的set方法,可以利用其關(guān)鍵字符當(dāng)屬性名。例如:var anim = ObjectAnimator.ofFloat(textView, "Alpha", 0f, 1f),因?yàn)閂iew中setAlpha方法,因此可使用Alpha當(dāng)屬性名。如果View中提供get方法,當(dāng)只傳入一個(gè)數(shù)值時(shí),可使用get方法獲取初始值。例如:var anim = ObjectAnimator.ofFloat(lineView, "Position", 1000f),因?yàn)閘ineView中有g(shù)etPosition方法,從該方法獲取的值可當(dāng)初始值使用。

? ? AnimatorSet繼承自Animator,可以將ValueAnimator和ObjectAnimator任意組合播放。可使用Play方法獲取Builder類,配合Builder類的四個(gè)方法實(shí)現(xiàn)動(dòng)畫的多種效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容