幀動畫是最容易實現的一種動畫,這種動畫更多的依賴於完善的UI資源,他的原理就是將一張張單獨的圖片連貫的進行播放,
從而在視覺上產生一種動畫的效果;有點相似於某些軟件製做gif動畫的方式。java
frame.gifandroid
如上圖中的京東加載動畫,代碼要作的事情就是把一幅幅的圖片按順序顯示,形成動畫的視覺效果。git
京東動畫實現github
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/a_0" android:duration="100" /> <item android:drawable="@drawable/a_1" android:duration="100" /> <item android:drawable="@drawable/a_2" android:duration="100" /> </animation-list>
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_frame_animation); ImageView animationImg1 = (ImageView) findViewById(R.id.animation1); animationImg1.setImageResource(R.drawable.frame_anim1); AnimationDrawable animationDrawable1 = (AnimationDrawable) animationImg1.getDrawable(); animationDrawable1.start(); }
能夠說,圖片資源決定了這種方式能夠實現怎樣的動畫canvas
在有些代碼中,咱們還會看到android:oneshot="false" ,這個oneshot 的含義就是動畫執行一次(true)仍是循環執行屢次。ide
這裏其餘幾個動畫實現方式都是同樣,無非就是圖片資源的差別。函數
補間動畫又能夠分爲四種形式,分別是 alpha(淡入淡出),translate(位移),scale(縮放大小),rotate(旋轉)。
補間動畫的實現,通常會採用xml 文件的形式;代碼會更容易書寫和閱讀,同時也更容易複用。oop
首先,在res/anim/ 文件夾下定義以下的動畫實現方式post
alpha_anim.xml 動畫實現動畫
<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:fromAlpha="1.0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toAlpha="0.0" />
scale.xml 動畫實現
<?xml version="1.0" encoding="utf-8"?> <scale xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:fromXScale="0.0" android:fromYScale="0.0" android:pivotX="50%" android:pivotY="50%" android:toXScale="1.0" android:toYScale="1.0"/>
而後,在Activity中
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim); img = (ImageView) findViewById(R.id.img); img.startAnimation(animation);
這樣就能夠實現ImageView alpha 透明變化的動畫效果。
也可使用set 標籤將多個動畫組合(代碼源自Android SDK API)
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@[package:]anim/interpolator_resource" android:shareInterpolator=["true" | "false"] > <alpha android:fromAlpha="float" android:toAlpha="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale="float" android:toYScale="float" android:pivotX="float" android:pivotY="float" /> <translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float" /> <rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float" /> <set> ... </set> </set>
能夠看到組合動畫是能夠嵌套使用的。
各個動畫屬性的含義結合動畫自身的特色應該很好理解,就不一一闡述了;這裏主要說一下interpolator 和 pivot。
Interpolator 主要做用是能夠控制動畫的變化速率 ,就是動畫進行的快慢節奏。
Android 系統已經爲咱們提供了一些Interpolator ,好比 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含義能夠在Android SDK 中查看。同時這個Interpolator也是能夠自定義的,這個後面還會提到。
pivot 決定了當前動畫執行的參考位置
pivot 這個屬性主要是在translate 和 scale 動畫中,這兩種動畫都牽扯到view 的「物理位置「發生變化,因此須要一個參考點。而pivotX和pivotY就共同決定了這個點;它的值能夠是float或者是百分比數值。
咱們以pivotX爲例,
pivotX取值 | 含義 |
---|---|
10 | 距離動畫所在view自身左邊緣10像素 |
10% | 距離動畫所在view自身左邊緣 的距離是整個view寬度的10% |
10%p | 距離動畫所在view父控件左邊緣的距離是整個view寬度的10% |
pivotY 也是相同的原理,只不過變成的縱向的位置。若是仍是不明白能夠參考源碼,在Tweened Animation中結合seekbar的滑動觀察rotate的變化理解。
rotate1.gif
有時候,動畫的屬性值可能須要動態的調整,這個時候使用xml 就不合適了,須要使用java代碼實現
private void RotateAnimation() { animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF, pxValue, Animation.RELATIVE_TO_SELF, pyValue); animation.setDuration(timeValue); if (keep.isChecked()) { animation.setFillAfter(true); } else { animation.setFillAfter(false); } if (loop.isChecked()) { animation.setRepeatCount(-1); } else { animation.setRepeatCount(0); } if (reverse.isChecked()) { animation.setRepeatMode(Animation.REVERSE); } else { animation.setRepeatMode(Animation.RESTART); } img.startAnimation(animation); }
這裏animation.setFillAfter決定了動畫在播放結束時是否保持最終的狀態;animation.setRepeatCount和animation.setRepeatMode 決定了動畫的重複次數及重複方式,具體細節可查看源碼理解。
好了,傳統動畫的內容就說到這裏了。
屬性動畫,顧名思義它是對於對象屬性的動畫。所以,全部補間動畫的內容,均可以經過屬性動畫實現。
首先咱們來看看如何用屬性動畫實現上面補間動畫的效果
private void RotateAnimation() { ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f); anim.setDuration(1000); anim.start(); } private void AlpahAnimation() { ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f); anim.setRepeatCount(-1); anim.setRepeatMode(ObjectAnimator.REVERSE); anim.setDuration(2000); anim.start(); }
這兩個方法用屬性動畫的方式分別實現了旋轉動畫和淡入淡出動畫,其中setDuration、setRepeatMode及setRepeatCount和補間動畫中的概念是同樣的。
能夠看到,屬性動畫貌似強大了許多,實現很方便,同時動畫可變化的值也有了更多的選擇,動畫所能呈現的細節也更多。
固然屬性動畫也是能夠組合實現的
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f); ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f); ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f); ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360); ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400); ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750); AnimatorSet set = new AnimatorSet(); set.playTogether(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim); // set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim); set.setDuration(3000); set.start();
能夠看到這些動畫能夠同時播放,或者是按序播放。
在上面實現屬性動畫的時候,咱們反覆的使用到了ObjectAnimator 這個類,這個類繼承自ValueAnimator,使用這個類能夠對任意對象的任意屬性進行動畫操做。而ValueAnimator是整個屬性動畫機制當中最核心的一個類;這點從下面的圖片也能夠看出。
valueanimator.png
屬性動畫核心原理,此圖來自於Android SDK API 文檔。
屬性動畫的運行機制是經過不斷地對值進行操做來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,咱們只須要將初始值和結束值提供給ValueAnimator,而且告訴它動畫所需運行的時長,那麼ValueAnimator就會自動幫咱們完成從初始值平滑地過渡到結束值這樣的效果。除此以外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設置監聽器等。
從上圖咱們能夠了解到,經過duration、startPropertyValue和endPropertyValue 等值,咱們就能夠定義動畫運行時長,初始值和結束值。而後經過start方法開始動畫。
那麼ValueAnimator 究竟是怎樣實現從初始值平滑過渡到結束值的呢?這個就是由TypeEvaluator 和TimeInterpolator 共同決定的。
具體來講,TypeEvaluator 決定了動畫如何從初始值過渡到結束值。
TimeInterpolator 決定了動畫從初始值過渡到結束值的節奏。
說的通俗一點,你天天早晨出門去公司上班,TypeEvaluator決定了你是坐公交、坐地鐵仍是騎車;而當你決定騎車後,TimeInterpolator決定了你一路上騎行的方式,你能夠勻速的一路騎到公司,你也能夠前半程騎得飛快,後半程騎得慢悠悠。
若是,仍是不理解,那麼就看下面的代碼吧。首先看一下下面的這兩個gif動畫,一個小球在屏幕上以 y=sin(x) 的數學函數軌跡運行,同時小球的顏色和半徑也發生着變化,能夠發現,兩幅圖動畫變化的節奏也是不同的。
anim1.gif
anim2.gif
若是不考慮屬性動畫,這樣的一個動畫純粹的使用Canvas+Handler的方式繪製也是有可能實現的。可是會複雜不少,並且加上各類線程,會帶來不少意想不到的問題。
這裏就經過自定義屬性動畫的方式看看這個動畫是如何實現的。
這個動畫最關鍵的三點就是 運動軌跡、小球半徑及顏色的變化;咱們就從這三個方面展開。最後咱們在結合Interpolator說一下TimeInterpolator的意義。
用TypeEvaluator 肯定運動軌跡#####
前面說了,TypeEvaluator決定了動畫如何從初始值過渡到結束值。這個TypeEvaluator是個接口,咱們能夠實現這個接口。
public class PointSinEvaluator implements TypeEvaluator { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { Point startPoint = (Point) startValue; Point endPoint = (Point) endValue; float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX()); float y = (float) (Math.sin(x * Math.PI / 180) * 100) + endPoint.getY() / 2; Point point = new Point(x, y); return point; } }
PointSinEvaluator 繼承了TypeEvaluator類,並實現了他惟一的方法evaluate;這個方法有三個參數,第一個參數fraction 表明當前動畫完成的百分比,這個值是如何變化的後面還會提到;第二個和第三個參數表明動畫的初始值和結束值。這裏咱們的邏輯很簡單,x的值隨着fraction 不斷變化,並最終達到結束值;y的值就是當前x值所對應的sin(x) 值,而後用x 和 y 產生一個新的點(Point對象)返回。
這樣咱們就可使用這個PointSinEvaluator 生成屬性動畫的實例了。
Point startP = new Point(RADIUS, RADIUS);//初始值(起點) Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//結束值(終點) final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP); valueAnimator.setRepeatCount(-1); valueAnimator.setRepeatMode(ValueAnimator.REVERSE); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentPoint = (Point) animation.getAnimatedValue(); postInvalidate(); } });
這樣咱們就完成了動畫軌跡的定義,如今只要調用valueAnimator.start() 方法,就會繪製出一個正弦曲線的軌跡。
顏色及半徑動畫實現#####
以前咱們說過,使用ObjectAnimator 能夠對任意對象的任意屬性進行動畫操做,這句話是不太嚴謹的,這個任意屬性還須要有get 和 set 方法。
public class PointAnimView extends View { /** * 實現關於color 的屬性動畫 */ private int color; private float radius = RADIUS; ..... }
這裏在咱們的自定義view中,定義了兩個屬性color 和 radius,並實現了他們各自的get set 方法,這樣咱們就可使用屬性動畫的特色實現小球顏色變化的動畫和半徑變化的動畫。
ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN, Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED); animColor.setRepeatCount(-1); animColor.setRepeatMode(ValueAnimator.REVERSE); ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f); animScale.setRepeatCount(-1); animScale.setRepeatMode(ValueAnimator.REVERSE); animScale.setDuration(5000); animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { radius = (float) animation.getAnimatedValue(); } });
這裏,咱們使用ObjectAnimator 實現對color 屬性的值按照ArgbEvaluator 這個類的規律在給定的顏色值之間變化,這個ArgbEvaluator 和咱們以前定義的PointSinEvaluator同樣,都是決定動畫如何從初始值過渡到結束值的,只不過這個類是系統自帶的,咱們直接拿來用就能夠,他能夠實現各類顏色間的自由過渡。
對radius 這個屬性使用了ValueAnimator,使用了其ofFloat方法實現了一系列float值的變化;同時爲其添加了動畫變化的監聽器,在屬性值更新的過程當中,咱們能夠將變化的結果賦給radius,這樣就實現了半徑動態的變化。
這裏radius 也可使用和color相同的方式,只須要把ArgbEvaluator 替換爲FloatEvaluator,同時修改動畫的變化值便可;使用添加監聽器的方式,只是爲了介紹監聽器的使用方法而已
好了,到這裏咱們已經定義出了全部須要的動畫,前面說過,屬性動畫也是能夠組合使用的。所以,在動畫啓動的時候,同時播放這三個動畫,就能夠實現圖中的效果了。
animSet = new AnimatorSet(); animSet.play(valueAnimator).with(animColor).with(animScale); animSet.setDuration(5000); animSet.setInterpolator(interpolatorType); animSet.start();
PointAnimView 源碼
public class PointAnimView extends View { public static final float RADIUS = 20f; private Point currentPoint; private Paint mPaint; private Paint linePaint; private AnimatorSet animSet; private TimeInterpolator interpolatorType = new LinearInterpolator(); /** * 實現關於color 的屬性動畫 */ private int color; private float radius = RADIUS; public PointAnimView(Context context) { super(context); init(); } public PointAnimView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public PointAnimView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public int getColor() { return color; } public void setColor(int color) { this.color = color; mPaint.setColor(this.color); } public float getRadius() { return radius; } public void setRadius(float radius) { this.radius = radius; } private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.TRANSPARENT); linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); linePaint.setColor(Color.BLACK); linePaint.setStrokeWidth(5); } @Override protected void onDraw(Canvas canvas) { if (currentPoint == null) { currentPoint = new Point(RADIUS, RADIUS); drawCircle(canvas); // StartAnimation(); } else { drawCircle(canvas); } drawLine(canvas); } private void drawLine(Canvas canvas) { canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint); canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint); canvas.drawPoint(currentPoint.getX(), currentPoint.getY(), linePaint); } public void StartAnimation() { Point startP = new Point(RADIUS, RADIUS); Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS); final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP); valueAnimator.setRepeatCount(-1); valueAnimator.setRepeatMode(ValueAnimator.REVERSE); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentPoint = (Point) animation.getAnimatedValue(); postInvalidate(); } }); // ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN, Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED); animColor.setRepeatCount(-1); animColor.setRepeatMode(ValueAnimator.REVERSE); ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f); animScale.setRepeatCount(-1); animScale.setRepeatMode(ValueAnimator.REVERSE); animScale.setDuration(5000); animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { radius = (float) animation.getAnimatedValue(); } }); animSet = new AnimatorSet(); animSet.play(valueAnimator).with(animColor).with(animScale); animSet.setDuration(5000); animSet.setInterpolator(interpolatorType); animSet.start(); } private void drawCircle(Canvas canvas) { float x = currentPoint.getX(); float y = currentPoint.getY(); canvas.drawCircle(x, y, radius, mPaint); } public void setInterpolatorType(int type ) { switch (type) { case 1: interpolatorType = new BounceInterpolator(); break; case 2: interpolatorType = new AccelerateDecelerateInterpolator(); break; case 3: interpolatorType = new DecelerateInterpolator(); break; case 4: interpolatorType = new AnticipateInterpolator(); break; case 5: interpolatorType = new LinearInterpolator(); break; case 6: interpolatorType=new LinearOutSlowInInterpolator(); break; case 7: interpolatorType = new OvershootInterpolator(); default: interpolatorType = new LinearInterpolator(); break; } } @TargetApi(Build.VERSION_CODES.KITKAT) public void pauseAnimation() { if (animSet != null) { animSet.pause(); } } public void stopAnimation() { if (animSet != null) { animSet.cancel(); this.clearAnimation(); } } }
Interpolator的概念其實咱們並不陌生,在補間動畫中咱們就使用到了。他就是用來控制動畫快慢節奏的;而在屬性動畫中,TimeInterpolator 也是相似的做用;TimeInterpolator 繼承自Interpolator。咱們能夠繼承TimerInterpolator 以本身的方式控制動畫變化的節奏,也可使用Android 系統提供的Interpolator。
下面都是系統幫咱們定義好的一些Interpolator,咱們能夠經過setInterpolator 設置不一樣的Interpolator。
系統自帶Interpolator
這裏咱們使用的Interpolator就決定了 前面咱們提到的fraction。變化的節奏決定了動畫所執行的百分比。不得不說,這麼ValueAnimator的設計的確是很巧妙。
這裏提一下,屬性動畫固然也可使用xml文件的方式實現,可是屬性動畫的屬性值通常會牽扯到對象具體的屬性,更可能是經過代碼動態獲取,因此xml文件的實現會有些不方便。
<set android:ordering="sequentially"> <set> <objectAnimator android:propertyName="x" android:duration="500" android:valueTo="400" android:valueType="intType"/> <objectAnimator android:propertyName="y" android:duration="500" android:valueTo="300" android:valueType="intType"/> </set> <objectAnimator android:propertyName="alpha" android:duration="500" android:valueTo="1f"/> </set>
使用方式:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim.property_animator); set.setTarget(myObject); set.start();
xml 文件中的標籤也和屬性動畫的類相對應。
ValueAnimator --- <animator> ObjectAnimator --- <objectAnimator> AnimatorSet --- <set>
這些就是屬性動畫的核心內容。如今使用屬性動畫的特性自定義動畫應該不是難事了。其他便籤的含義,結合以前的內容應該不難理解了。
相較於傳統動畫,屬性動畫有不少優點。那是否意味着屬性動畫能夠徹底替代傳統動畫呢。其實否則,兩種動畫都有各自的優點,屬性動畫如此強大,也不是沒有缺點。
補間動畫點擊事件
屬性動畫點擊事件
從上面兩幅圖比較能夠發現,補間動畫中,雖然使用translate將圖片移動了,可是點擊原來的位置,依舊能夠發生點擊事件,而屬性動畫卻不是。所以咱們能夠肯定,屬性動畫纔是真正的實現了view的移動,補間動畫對view的移動更像是在不一樣地方繪製了一個影子,實際的對象仍是處於原來的地方。
當咱們把動畫的repeatCount設置爲無限循環時,若是在Activity退出時沒有及時將動畫中止,屬性動畫會致使Activity沒法釋放而致使內存泄漏,而補間動畫卻沒有問題。所以,使用屬性動畫時切記在Activity執行 onStop 方法時順便將動畫中止。(對這個懷疑的同窗能夠本身經過在動畫的Update 回調方法打印日誌的方式進行驗證)。
xml 文件實現的補間動畫,複用率極高。在Activity切換,窗口彈出時等情景中有着很好的效果。
使用幀動畫時須要注意,不要使用過多特別大的圖,容易致使內存不足。
好了,關於Android 動畫的總結就到這裏。
做者:IAM四十二 連接:https://www.jianshu.com/p/420629118c10 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。