Android動畫機制全解析
導論
本文著重講解Android3.0後推出的屬性動畫框架Property Animation——Animator。
產生原因
3.0之前已有的動畫框架——Animation存在一些侷限性, Animation框架定義了透明度,旋轉,縮放和位移幾種常見的動畫,而且控制的是整個View,實現原理是每次繪製檢視時View所在的ViewGroup中的drawChild函式獲取該View的Animation的Transformation值,然後呼叫canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成,繼續呼叫invalidate()函式,啟動下次繪製來驅動動畫,動畫過程中的幀之間間隙時間是繪製函式所消耗的時間,可能會導致動畫消耗比較多的CPU資源,最重要的是,動畫改變的只是顯示,並不能相應事件。
而在Animator框架中使用最多的是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator進行更精細化控制,只控制一個物件的一個屬性值,多個ObjectAnimator組合到AnimatorSet形成一個動畫。而且ObjectAnimator能夠自動驅動,可以呼叫setFrameDelay(longframeDelay)設定動畫幀之間的間隙時間,調整幀率,減少動畫過程中頻繁繪製介面,而在不影響動畫效果的前提下減少CPU資源消耗。因此,Anroid推出的強大的屬性動畫框架,基本可以實現所有的動畫效果。
強大的原因
因為屬性動畫框架操作的是真實的屬性值,直接變化了物件的屬性,因此可以很靈活的實現各種效果,而不侷限於以前的4種動畫效
ObjectAnimator
ObjectAnimator是屬性動畫框架中最重要的實行類,建立一個ObjectAnimator只需通過他的靜態工廠類直接返回一個ObjectAnimator物件。傳的引數包括一個物件和物件的屬性名字,但這個屬性必須有get和set函式,內部會通過java反射機制來呼叫set函式修改物件屬性值。還包括屬性的初始值,最終值,還可以呼叫setInterpolator設定曲線函式。
ObjectAnimator例項
ObjectAnimator .ofFloat(view, "rotationX", 0.0F, 360.0F) .setDuration(1000) .start();
這個例子很簡單,針對view的屬性rotationX進行持續時間為1000ms的0到360的角度變換。
PS:可操縱的屬性引數:x/y;scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY等等。
PS:X是View最終的位置、translationX為最終位置與佈局時初始位置的差。所以若就用translationX即為在原來基礎上移動多少,X為最終多少。getX()的值為getLeft()與getTranslationX()的和。
動畫繪製過程的監聽
animator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator arg0) { } });
該方法用來監聽動畫繪製過程中的每一幀的改變,通過這個方法,我們可以在動畫重繪的過程中,實現自己的邏輯。
同時修改多個屬性值
當然這個可以使用Animationset來實現,這裡我們使用一種取巧的方法來實現:
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "xxx", 1.0F, 0.0F) .setDuration(500); anim.start(); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { floatcVal = (Float) animation.getAnimatedValue(); view.setAlpha(cVal); view.setScaleX(cVal); view.setScaleY(cVal); } });
我們可以監聽一個並不存在的屬性,而在監聽動畫更新的方法中,去修改view的屬性,監聽一個不存在的屬性的原因就是,我們只需要動畫的變化值,通過這個值,我們自己來實現要修改的效果,實際上,更直接的方法,就是使用ValueAnimator來實現,其實ObjectAnimator就是ValueAnimator的子類,這個在下面會具體講到。
為不具有get/set方法的屬性提供修改方法
Google在應用層為我們提供了2種解決方法,一種是通過自己寫一個包裝類,來為該屬性提供get/set方法,還有一種是通過ValueAnimator來實現,ValueAnimator的方法我們在下面會具體講解,這裡講解下如何使用自定義的包裝類來給屬性提供get/set方法。
包裝類
private static class WrapperView { private View mTarget; public WrapperView(View target) { mTarget = target; } public int getWidth() { return mTarget.getLayoutParams().width; } public void setWidth(int width) { mTarget.getLayoutParams().width = width; mTarget.requestLayout(); } }
使用方法:
ViewWrapper wrapper = new ViewWrapper(mButton); ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
這樣就間接給他加上了get/set方法,從而可以修改其屬性實現動畫效果。
多動畫效果的另一種實現方法——propertyValuesHolder
public void propertyValuesHolder(View view) { PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f); PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f); PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f); ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ) .setDuration(1000).start(); }
ValueAnimator
說簡單點,ValueAnimator就是一個數值產生器,他本身不作用於任何一個物件,但是可以對產生的值進行動畫處理。
ValueAnimator animator = ValueAnimator.ofFloat(0, 100); animator.setTarget(view); animator.setDuration(1000).start(); animator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float value = (Float) animation.getAnimatedValue(); imageView.setTranslationY(value); } });
通過這個動畫我們可以發現,和我們在上面提供的使用ObjectAnimator的方法很像,的確,我前面說這個才是專業的寫法,就是這個原因,動畫生成的原理就是通過差值器計算出來的一定規律變化的數值作用到物件上來實現物件效果的變化,因此我們可以使用ObjectAnimator來生成這些數,然後在動畫重繪的監聽中,完成自己的效果。
ValueAnimator是計算動畫過程中變化的值,包含動畫的開始值,結束值,持續時間等屬性。但並沒有把這些計算出來的值應用到具體的物件上面,所以也不會有什麼的動畫顯示出來。要把計算出來的值應用到物件上,必須為ValueAnimator註冊一個監聽器ValueAnimator.AnimatorUpdateListener,該監聽器負責更新物件的屬性值。在實現這個監聽器的時候,可以通過getAnimatedValue()的方法來獲取當前幀的值。
ValueAnimator封裝了一個TimeInterpolator,TimeInterpolator定義了屬性值在開始值與結束值之間的插值方法。ValueAnimator還封裝了一個TypeAnimator,根據開始、結束值與TimeIniterpolator計算得到的值計算出屬性值。ValueAnimator根據動畫已進行的時間跟動畫總時間(duration)的比計算出一個時間因子(0~1),然後根據TimeInterpolator計算出另一個因子,最後TypeAnimator通過這個因子計算出屬性值,例如在10ms時(total 40ms):
首先計算出時間因子,即經過的時間百分比:t=10ms/40ms=0.25
經插值計算(inteplator)後的插值因子:大約為0.15,如果使用了AccelerateDecelerateInterpolator,計算公式為(input即為時間因子):
(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
最後根據TypeEvaluator計算出在10ms時的屬性值:0.15*(40-0)=6pixel。如果使用TypeEvaluator為FloatEvaluator,計算方法為 :
public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); }
引數分別為上一步的插值因子,開始值與結束值。
ValueAnimator與ObjectAnimator例項
package com.example.animtest; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.Activity; import android.graphics.PointF; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.animation.BounceInterpolator; import android.view.animation.LinearInterpolator; import android.widget.ImageView; public class AnimateFreeFall extends Activity { private int screenHeight; private int screenWidth; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.animate_free_fall); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); screenHeight = metrics.heightPixels; screenWidth = metrics.widthPixels; imageView = (ImageView) findViewById(R.id.im); } public void clean(View view) { imageView.setTranslationX(0); imageView.setTranslationY(0); } public void freefall(View view) { final ValueAnimator animator = ValueAnimator.ofFloat(0, screenHeight - imageView.getHeight()); animator.setTarget(view); animator.setInterpolator(new BounceInterpolator()); animator.setDuration(1000).start(); animator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float value = (Float) animation.getAnimatedValue(); imageView.setTranslationY(value); } }); } public void parabola(View view) { ValueAnimator animator = ValueAnimator.ofObject( new TypeEvaluator<PointF>() { @Override public PointF evaluate(float fraction, PointF arg1, PointF arg2) { PointF p = new PointF(); p.x = fraction * screenWidth; p.y = fraction * fraction * 0.5f * screenHeight * 4f * 0.5f; return p; } }, new PointF(0, 0)); animator.setDuration(800); animator.setInterpolator(new LinearInterpolator()); animator.start(); animator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { PointF p = (PointF) animator.getAnimatedValue(); imageView.setTranslationX(p.x); imageView.setTranslationY(p.y); } }); } }
效果如下圖:
有一點需要注意的是,由於ofInt,ofFloat等無法使用,我們自定義了一個TypeValue,每次根據當前時間返回一個PointF物件,(PointF和Point的區別就是x,y的單位一個是float,一個是int point float的意思)PointF中包含了x,y的當前位置,然後在更新監聽中更新。
自定義TypeEvaluator傳入的泛型可以根據自己的需求,自己設計個Bean。
動畫事件的監聽
通過監聽這個事件在屬性的值更新時執行相應的操作,對於ValueAnimator一般要監聽此事件執行相應的動作,不然Animation沒意義(但是可用於計時),在ObjectAnimator(繼承自ValueAnimator)中會自動更新屬性,所以不必監聽。在函式中會傳遞一個ValueAnimator引數,通過此引數的getAnimatedValue()取得當前動畫屬性值。
PS:根據應用動畫的物件或屬性的不同,可能需要在onAnimationUpdate函式中呼叫invalidate()函式重新整理檢視通過動畫的各種狀態,我們可以監聽動畫的各種狀態。
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.5f); anim.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } }); anim.start();
可以看見,API提供了開始、重複、結束、取消等各種狀態的監聽,同時,API還提供了一種簡單的監聽方法,可以不用監聽所有的事件:
anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { } });
通過AnimatorListenerAdapter來選擇你需要監聽的事件
動畫監聽的例項應用
package com.example.animtest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ImageView; public class AnimateMoveInSecond extends Activity { private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.animate_move_in_second); imageView = (ImageView) findViewById(R.id.imageView1); } public void doit(View view) { ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1.0f, 0f); animator.setDuration(1000); animator.start(); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1.0f); animator.setDuration(1000); animator.start(); imageView.setTranslationY(400); } }); } }
效果如下圖:
AnimatorSet
AnimatorSet用於實現多個動畫的協同作用。效果如下:
ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "scaleX", 1f, 2f); ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "scaleY", 1f, 2f); ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView, "translationY", 0f, 500f); AnimatorSet set = new AnimatorSet(); set.setDuration(1000); set.playTogether(animator1, animator2, animator3); set.start();
AnimatorSet中有一系列的順序控制方法:playTogether、playSequentially、animSet.play().with()、defore()、after()等。用來實現多個動畫的協同工作方式。
使用xml來建立動畫
屬性動畫於以前的動畫一樣,也支援通過xml檔案來建立動畫,下面是一個簡單的例子:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:propertyName="scaleX" android:valueFrom="1.0" android:valueTo="2.0" android:valueType="floatType" > </objectAnimator>
public void scaleX(View view) { // 載入動畫 Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex); anim.setTarget(mMv); anim.start(); }
佈局動畫
佈局動畫是指ViewGroup在佈局時產生的動畫效果
LayoutTransition動畫
通過LayoutTransition來實現容器在新增子view的時候的動畫過渡效果:
package com.example.animtest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.Keyframe; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.LinearLayout; public class AnimateLayoutTransition extends Activity { private LinearLayout ll; private LayoutTransition mTransition = new LayoutTransition(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.animate_layout_transition); ll = (LinearLayout) findViewById(R.id.ll); setupCustomAnimations(); ll.setLayoutTransition(mTransition); } public void add(View view) { final Button button = new Button(this); ll.addView(button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { ll.removeView(button); } }); } // 生成自定義動畫 private void setupCustomAnimations() { // 動畫:CHANGE_APPEARING // Changing while Adding PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1); PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1); PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1); PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1); PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f); PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f); final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder( this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX, pvhScaleY).setDuration( mTransition.getDuration(LayoutTransition.CHANGE_APPEARING)); mTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn); changeIn.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator anim) { View view = (View) ((ObjectAnimator) anim).getTarget(); // View也支援此種動畫執行方式了 view.setScaleX(1f); view.setScaleY(1f); } }); // 動畫:CHANGE_DISAPPEARING // Changing while Removing Keyframe kf0 = Keyframe.ofFloat(0f, 0f); Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f); Keyframe kf2 = Keyframe.ofFloat(1f, 0f); PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe( "rotation", kf0, kf1, kf2); final ObjectAnimator changeOut = ObjectAnimator .ofPropertyValuesHolder(this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhRotation) .setDuration( mTransition .getDuration(LayoutTransition.CHANGE_DISAPPEARING)); mTransition .setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut); changeOut.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator anim) { View view = (View) ((ObjectAnimator) anim).getTarget(); view.setRotation(0f); } }); // 動畫:APPEARING // Adding ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 90f, 0f).setDuration( mTransition.getDuration(LayoutTransition.APPEARING)); mTransition.setAnimator(LayoutTransition.APPEARING, animIn); animIn.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator anim) { View view = (View) ((ObjectAnimator) anim).getTarget(); view.setRotationY(0f); } }); // 動畫:DISAPPEARING // Removing ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotationX", 0f, 90f).setDuration( mTransition.getDuration(LayoutTransition.DISAPPEARING)); mTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut); animOut.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator anim) { View view = (View) ((ObjectAnimator) anim).getTarget(); view.setRotationX(0f); } }); } }
上面的例子中自定義了 LayoutTransition來修改預設的過渡動畫,如果保持預設則使用系統預設的動畫效果。
過渡的型別一共有四種:
LayoutTransition.APPEARING 當一個View在ViewGroup中出現時,對此View設定的動畫
LayoutTransition.CHANGE_APPEARING當一個View在ViewGroup中出現時,對此View對其他View位置造成影響,對其他View設定的動畫
LayoutTransition.DISAPPEARING當一個View在ViewGroup中消失時,對此View設定的動畫
LayoutTransition.CHANGE_DISAPPEARING當一個View在ViewGroup中消失時,對此View對其他View位置造成影響,對其他View設定的動畫
LayoutTransition.CHANGE 不是由於View出現或消失造成對其他View位置造成影響,然後對其他View設定的動畫。
注意動畫到底設定在誰身上,此View還是其他View。
AnimateLayoutChanges動畫
ViewGroup的xml屬性中有一個預設的animateLayoutChanges屬性,設定該屬性,可以新增ViewGroup增加view的過渡效果:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="match_parent" android:animateLayoutChanges="true" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="add" android:text="Add Button" /> </LinearLayout>
LayoutAnimation動畫
通過設定LayoutAnimation也同樣可以實現佈局動畫效果,例項如下:
package com.example.animtest; import android.app.Activity; import android.os.Bundle; import android.view.animation.LayoutAnimationController; import android.view.animation.ScaleAnimation; import android.widget.LinearLayout; public class AnimateLayoutAnimation extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.animate_layout_animation); LinearLayout ll = (LinearLayout) findViewById(R.id.ll); ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1); sa.setDuration(2000); // 第二個引數dely : the delay by which each child's animation must be offset LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F); // 設定顯示的順序 這個必須要在dely不為0的時候才有效 lac.setOrder(LayoutAnimationController.ORDER_NORMAL); ll.setLayoutAnimation(lac); } }
View的animate方法
3.0後android對View也提供了直接作用的動畫API:
view.animate().alpha(0).y(100).setDuration(1000) .withStartAction(new Runnable() { @Override public void run() { } }).withEndAction(new Runnable() { @Override public void run() { runOnUiThread(new Runnable() { @Override public void run() { } }); } }).start();
Interpolators(插值器)
插值器和估值器,是實現非線性動畫的基礎,瞭解這些東西,才能作出不一樣的動畫效果。所謂的插值器,就是通過一些數學物理公式,計算出一些數值,提供給動畫來使用。就好比我們定義了起始值是0,結束值是100,但是這0到100具體是怎麼變化的呢,這就是插值器產生的結果,線性的,就是勻速增長,加速的,就是按加速度增長。這些增加的演算法公式,已經不需要我們來自己設計了,Android內建了7種插值器,基本可以滿足需求,當然你也可以自定新的插值器。
AccelerateInterpolator 加速
Decelerate 減速
AccelerateDecelerateInterpolator 開始,和結尾都很慢,但是,中間加速
AnticipateInterpolator 開始向後一點,然後,往前拋
OvershootInterpolator 往前拋超過一點,然後返回來
AnticipateOvershootInterpolator 開始向後一點,往前拋過點,然後返回來
BounceInterpolator 結束的時候彈一下
LinearInterpolator 預設 勻速
TypeEvalutors (估值器)
根據屬性的開始、結束值與TimeInterpolation計算出的因子計算出當前時間的屬性值,android提供了以下幾個evalutor:
IntEvaluator:屬性的值型別為int;
FloatEvaluator:屬性的值型別為float;
ArgbEvaluator:屬性的值型別為十六進位制顏色值;
TypeEvaluator:一個介面,可以通過實現該介面自定義Evaluator。
自定義TypeEvalutor很簡單,只需要實現一個方法,如FloatEvalutor的定義:
public class FloatEvaluator implements TypeEvaluator { public Object evaluate(float fraction, Object startValue, Object endValue) { float startFloat = ((Number) startValue).floatValue(); return startFloat + fraction * (((Number) endValue).floatValue() - startFloat); } }
根據動畫執行的時間跟應用的Interplator,會計算出一個0~1之間的因子,即evalute函式中的fraction引數。
KeyFrame
keyFrame是一個 時間/值 對,通過它可以定義一個在特定時間的特定狀態,即關鍵幀,而且在兩個keyFrame之間可以定義不同的Interpolator,就好像多個動畫的拼接,第一個動畫的結束點是第二個動畫的開始點。KeyFrame是抽象類,要通過ofInt(),ofFloat(),ofObject()獲得適當的KeyFrame,然後通過PropertyValuesHolder.ofKeyframe獲得PropertyValuesHolder物件,如以下例子:
Keyframe kf0 = Keyframe.ofInt(0, 400); Keyframe kf1 = Keyframe.ofInt(0.25f, 200); Keyframe kf2 = Keyframe.ofInt(0.5f, 400); Keyframe kf4 = Keyframe.ofInt(0.75f, 100); Keyframe kf3 = Keyframe.ofInt(1f, 500); PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3); ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation); rotationAnim.setDuration(2000);
上述程式碼的意思為:設定btn物件的width屬性值使其:
開始時 Width=400
動畫開始1/4時 Width=200
動畫開始1/2時 Width=400
動畫開始3/4時 Width=100
動畫結束時 Width=500
第一個引數為時間百分比,第二個引數是在第一個引數的時間時的屬性值。
定義了一些Keyframe後,通過PropertyValuesHolder類的方法ofKeyframe一個PropertyValuesHolder物件,然後通過ObjectAnimator.ofPropertyValuesHolder獲得一個Animator物件。
用下面的程式碼可以實現同樣的效果(上述程式碼時間值是線性,變化均勻):
ObjectAnimator oa=ObjectAnimator.ofInt(btn2, "width", 400,200,400,100,500); oa.setDuration(2000); oa.start();
相關文章
- TouchEvent事件分發機制全解析事件
- Flutter 事件機制 - Future 和 MicroTask 全解析Flutter事件
- Android動畫全解Android動畫
- Android全面解析之Window機制Android
- Android事件分發機制解析Android事件
- Android的Handler訊息機制 解析Android
- Android全面解析之Context機制AndroidContext
- Android AccessibilityService機制原始碼解析Android原始碼
- Android 事件分發機制原始碼解析Android事件原始碼
- RecyclerView 原始碼深入解析——繪製流程、快取機制、動畫等View原始碼快取動畫
- iOS隱式動畫機制iOS動畫
- Android 9.0 原始碼_機制篇 -- 全面解析 HandlerAndroid原始碼
- Android訊息機制不完全解析(上) .Android
- Android訊息機制不完全解析(下) .Android
- 屬性動畫 ValueAnimator 執行原理全解析動畫
- Handler機制解析
- Android View動畫和屬性動畫簡單解析:AndroidView動畫
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- android permission 許可權與安全機制解析(下)Android
- Android多執行緒基礎 解析Handler機制Android執行緒
- android permission許可權與安全機制解析(上)Android
- Android轉場動畫深度解析(1)Android動畫
- Android轉場動畫深度解析(2)Android動畫
- Android轉場動畫深度解析(3)Android動畫
- 自制android gif動畫解析器Android動畫
- Android fragment原始碼全解析AndroidFragment原始碼
- SPDK QOS機制解析
- Oracle SCN機制解析Oracle
- Android包管理機制(五)APK是如何被解析的AndroidAPK
- Android Volley 原始碼解析(二),探究快取機制Android原始碼快取
- Android事件分發機制之原始碼完美解析(上)Android事件原始碼
- Android原始碼解析之一 非同步訊息機制Android原始碼非同步
- 從零開始仿寫一個抖音App——Android繪製機制以及Surface家族原始碼全解析APPAndroid原始碼
- CSS動畫屬性關鍵幀keyframes全解析CSS動畫
- Android原始碼解析(二)動畫篇-- ObjectAnimatorAndroid原始碼動畫Object
- MySQL組複製(MGR)全解析 Part 3 組複製機制細節MySql
- Android基礎之Activity全解析Android
- Android Handler訊息傳遞機制:圖文解析工作原理Android