引言
????屬性動(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 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
????}
}
? ? 在某些情況下也許你并不需要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傳入的值是如何變化的。
? ? 使用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.2 ofObject
????ofObject構(gòu)造并返回一個(gè)在Object對(duì)象之間變化的ValueAnimator。通過(guò)下面圖1-6源碼可以看出來(lái),ofObject至少需要傳兩個(gè)參數(shù)。
????evaluator 傳入的是 null,Object只能傳入兩種類型,一種是float,另一種是int類型,傳入其他的類型會(huì)把報(bào)錯(cuò)。可以在動(dòng)畫開始時(shí)初始化中看到相關(guān)的操作,源碼如下:
? ? 再往下查看源碼,可以看出來(lái)sIntEvaluator是一個(gè)IntEvaluator,sFloatEvaluator是一個(gè)FloatEvaluator。
????如果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è)方法。
? ? 以IntEvaluator為例子,可以看出來(lái)IntEvaluator實(shí)現(xiàn)了TypeEvaluator。
? ? 從上面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。
? ? 那PropertyValuesHolder又是什么呢?PropertyValuesHolder類保存有關(guān)屬性的信息以及該屬性在動(dòng)畫中應(yīng)該呈現(xiàn)的值。PropertyValuesHolder對(duì)象可用于使用ValueAnimator或ObjectAnimator創(chuàng)建并行操作多個(gè)不同屬性的動(dòng)畫。
? ??PropertyValuesHolder的方法有很多,圖1-11的方法是最重要的。
? ? 首先看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的值。
? ? 此時(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ì)象的值。
? ? 再來(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所示。
? ? 再看看具體代碼是怎么寫的,代碼如下所示:
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()
}
????上面代碼中,使用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()
? ? 再來(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所示:
? ? 如果不提供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所示:
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í)行。
? ? 如果有需要,還可以繼續(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)畫的多種效果。