其實動畫這個東西我已經瞭解過很長一段時間了,但是一直沒系統的整理過。關於android中的各種動畫雖然都會用,但總怕自己會慢慢遺忘。這回看了幾篇動畫分析的文章,自己也學到了一些東西,在此就梳理一下。
參考博文如下,感謝大神們的分享:
http://www.open-open.com/lib/view/open1329994048671.html
http://www.tuicool.com/articles/yeM3my
http://blog.csdn.net/singwhatiwanna/article/details/17841165
http://blog.csdn.net/lmj623565791/article/details/38067475
http://blog.csdn.net/lmj623565791/article/details/38092093
注意:所有view的動畫就是被限制在它的父控件中的,即使你做了view的移動,它也不可能顯示在父控件的外邊。也就是說父控件是一個舞臺,演員可以在舞臺上到處走動,但如果超過了舞臺,那麼觀衆就看不到了。
一、View Animation(Tween Animation)
View Animation(Tween Animation):也可稱爲補間動畫(Tween Animation),給出兩個關鍵幀,通過一些算法將給定屬性值在給定的時間內在兩個關鍵幀間漸變。
View animation只能應用於View對象,而且只支持一部分屬性,如支持縮放旋轉而不支持背景顏色的改變。
對於View animation,它只是改變了View對象繪製的位置,注意這是“繪製”,而不是實際的位置。比如你讓一個button變成它的兩倍大小,但是它能接受你點擊的區域還是原來的button區域,沒有做任何改變。
View Animation支持設定多種動畫樣式,也可以設定這些動畫的執行順序,支持通過xml和代碼兩種方式來設置動畫效果。
動畫舉例
【 By XML 】
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:shareInterpolator="true" android:startOffset="50"> <alpha android:duration="200" android:fromAlpha="1.0" android:toAlpha="0.0" /> </set>
Animation aimation = AnimationUtils.loadAnimation(this, R.anim.anim); final ImageView imageView = (ImageView) findViewById(R.id.imageView_id); imageView.startAnimation(aimation);
可以對動畫添加監聽器
aimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { animation = null; } @Override public void onAnimationRepeat(Animation animation) { } });
如果你是用AnimationSet設置動畫的話,animationSet也是繼承自Animation,所以也有setAnimationListener的方法
詳細代碼可以參考:http://www.cnblogs.com/tianzhijiexian/p/3983616.html
【 By JAVA 】
通過java代碼來做的,可以參考這篇文章。其實也就是各種類繼承Animation,然後有自己獨特的方法,也可以通過AnimationSet進行各種設置。
http://www.cnblogs.com/tianzhijiexian/p/3981241.html
二、Drawable Animation(Frame Animation)
Drawable Animation(Frame Animation):幀動畫,就像GIF圖片,通過一系列Drawable依次顯示來模擬動畫的效果。在XML中的定義方式如下:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust2" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
必須以<animation-list>爲根元素,以<item>表示要輪換顯示的圖片,duration屬性表示各項顯示的時間。XML文件要放在/res/drawable/目錄下。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.main); imageView = (ImageView) findViewById(R.id.imageView1); imageView.setBackgroundResource(R.drawable.drawable_anim); AnimationDrawable anim = (AnimationDrawable) imageView.getBackground(); } public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { anim.stop(); anim.start(); return true; } return super.onTouchEvent(event); }
我在實驗中遇到兩點問題:
- 要在代碼中調用Imageview的setBackgroundResource方法,如果直接在XML佈局文件中設置其src屬性當觸發動畫時會FC。
- 在動畫start()之前要先stop(),不然在第一次動畫之後會停在最後一幀,這樣動畫就只會觸發一次。
- 最後一點是SDK中提到的,不要在onCreate中調用start,因爲AnimationDrawable還沒有完全跟Window相關聯,如果想要界面顯示時就開始動畫的話,可以在onWindowFoucsChanged()中調用start()。
三、Property Animation
屬性動畫,這個是在Android 3.0中才引進的,它更改的是對象的實際屬性,如Button的縮放,Button的位置與大小屬性值都改變了。而且Property Animation不止可以應用於View,還可以應用於任何對象(Object)。Property Animation只是表示一個值在一段時間內的改變,當值改變時要做什麼事情完全是你自己決定的。
在Property Animation中,可以對動畫應用以下屬性:
- Duration:動畫的持續時間,單位ms
- TimeInterpolation:屬性值的計算方式,如先快後慢,是一個接口。用來設置插值器
- TypeEvaluator:根據屬性的開始、結束值與TimeInterpolation計算出的因子計算出“當前”時間的屬性值,這個值可以在AnimationUpdate中得到
- Repeat Country and behavoir:重複次數與方式,如播放3次、5次、無限循環,可以此動畫一直重複,或播放完時再反向播放
- Animation sets:動畫集合,即可以同時對一個對象應用幾個動畫,這些動畫可以同時播放也可以對不同動畫設置不同開始偏移
- Frame refreash delay:多少時間刷新一次,即每隔多少時間計算一次屬性值,默認爲10ms,最終刷新時間還受系統進程調度與硬件的影響
四、ValueAnimator
ValueAnimator包含Property Animation動畫的所有核心功能,如動畫時間,開始、結束屬性值,相應時間屬性值計算方法等。它其實就是一個計算器,並不能實際執行動畫效果。它可以計算出動畫要執行的時間,每隔幾毫秒刷新一次等等,但具體如何執行動畫,它是不管的。你需要在ValueAnimator的ValueAnimator.onUpdateListener監聽器中進行設置。
這裏給ValueAnimator對象設置了一個值,因爲是一個值,所以會默認爲是最終的結果值,動畫默認從當前的值到你設定的這個值。如果你設定兩個值,那麼它就意味着是從第一個值到另一個值進行動畫。
// set one value,it is final value. // If you set two values in it.It means animation will start from first value to Second value. ValueAnimator animator = ValueAnimator.ofFloat(1f); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { } });
animator.start();
這個AnimationUpdateListener將會在動畫執行過程中觸發。如果你沒在這裏做任何處理,那麼即使是執行了(start())也不會有任何動畫的。
實際運用——自由落體 & 拋物線
如果我希望小球拋物線運動【實現拋物線的效果,水平方向100px/s,垂直方向加速度200px/s*s 】,分析一下,貌似只和時間有關係,但是根據時間的變化,橫向和縱向的移動速率是不同的,我們該咋實現呢?此時就要重寫TypeValue的時候了,因爲我們在時間變化的同時,需要返回給對象兩個值,x當前位置,y當前位置。
/** * 拋物線 * @param view */ public void paowuxian(View view) { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setDuration(3000); valueAnimator.setObjectValues(new PointF(0, 0)); valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.setEvaluator(new TypeEvaluator<PointF>() { // fraction = t / duration @Override public PointF evaluate(float fraction, PointF startValue, PointF endValue) { Log.e(TAG, fraction * 3 + ""); // x方向200px/s ,則y方向0.5 * 10 * t PointF point = new PointF(); point.x = 200 * fraction * 3; point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3); return point; } }); valueAnimator.start(); valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { PointF point = (PointF) animation.getAnimatedValue(); mBlueBall.setX(point.x); mBlueBall.setY(point.y); } }); }
可以看到,因爲ofInt,ofFloat等無法使用,我們自定義了一個TypeValue,每次根據當前時間返回一個PointF對象,(PointF和Point的區別就是x,y的單位一個是float,一個是int;RectF,Rect也是)PointF中包含了x、y的當前位置,然後我們在監聽器中獲取,動態設置屬性。
五、ObjectAnimator
實際應用中一般都會用ObjectAnimator來產生某一對象的動畫,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,應該滿足以下條件:
- 對象應該有一個setter函數:set<PropertyName>(駝峯命名法)
- 如上面的例子中,像ofFloat之類的工場方法,第一個參數爲對象名,第二個爲屬性名,後面的參數爲可變參數,如果values…參數只設置了一個值的話,那麼會假定爲目的值,屬性值的變化範圍爲當前值到目的值,爲了獲得當前值,該對象要有相應屬性的getter方法:get<PropertyName>
- 如果有getter方法,其應返回值類型應與相應的setter方法的參數類型一致。
如果上述條件不滿足,則不能用ObjectAnimator,應用ValueAnimator代替。
下面是將imageview進行透明度漸變的例子。
ObjectAnimator oa=ObjectAnimator.ofFloat(imageview, "alpha", 0f, 1f); oa.setDuration(3000); oa.start();
根據應用動畫的對象或屬性的不同,可能需要在onAnimationUpdate函數中調用invalidate()函數刷新視圖。
下面分析下屬性動畫的原理:
屬性動畫要求動畫作用的對象提供該屬性的get和set方法,屬性動畫根據你傳遞的該熟悉的初始值和最終值,以動畫的效果多次去調用set方法,每次傳遞給set方法的值都不一樣,確切來說是隨着時間的推移,所傳遞的值越來越接近最終值。總結一下,你對object的屬性xxx做動畫,如果想讓動畫生效,要同時滿足兩個條件:
- object必須要提供setXxx方法,如果動畫的時候沒有傳遞初始值,那麼還要提供getXxx方法,因爲系統要去拿xxx屬性的初始值(如果這條不滿足,程序直接Crash)
- object的setXxx對屬性xxx所做的改變必須能夠通過某種方法反映出來,比如會帶來ui的改變啥的(如果這條不滿足,動畫無效果但不會Crash)
以上條件缺一不可
那麼如果我們的object沒辦法滿足這個條件呢?比如button的setWidth方法僅僅是設置它的最小寬度(minWidth),對於實際寬度不會產生任何影響。如果讓它實際改變就需要用
btn.getLayoutParams().width = xxx;
來設置,但這種通過方法來獲得public值的方法又不適合ObjecAnimator的應用場景,改怎麼辦呢?
針對上述問題,Google告訴我們有3中解決方法:
1. 給你的對象加上get和set方法,如果你有權限的話
對於View我們沒權限給其添加各種屬性,所以這種方法僅僅適合於自己定義的Object對象
2. 用一個類來包裝原始對象,間接爲其提供get和set方法
這個方法很好,你可以寫一個類繼承那個Object對象,在子類中添加各種set、get方法,更好的辦法是寫一個包裝類,傳入一個object對象,然後給其添加各種方法。
private void performAnimate() { ViewWrapper wrapper = new ViewWrapper(mButton); ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start(); } @Override public void onClick(View v) { if (v == mButton) { performAnimate(); } } private static class ViewWrapper { private View mTarget; public ViewWrapper(View target) { mTarget = target; } public int getWidth() { return mTarget.getLayoutParams().width; } public void setWidth(int width) { mTarget.getLayoutParams().width = width; mTarget.requestLayout(); } }
上面類中ViewWrapper這個內部類就是一個包裝類,它的構造函數傳入一個View對象,然後提供了set和get方法,這樣我們就可以對它進行動畫的操作了
3. 採用ValueAnimator,監聽動畫過程,自己實現屬性的改變
這個方法就有些彆扭了,但擴展性是最強的。ValueAnimator我們在上面已經介紹過了,下面是在動畫執行過程中能的得到的東西。
ValueAnimator animator = ValueAnimator.ofFloat(1, 1); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Integer currentValue = (Integer)animation.getAnimatedValue(); // current Value(0~100) float fraction = valueAnimator.getAnimatedFraction(); // progress of animation } });
getAnimatedFraction() 這個可以得到當前動畫的進度,得到的是浮點型數據很有用。api12中加入的,感覺比getAnimationValue有用
getAnimatedValue(String propertyName) 得到某個特定屬性當前的值,這個可用性就很高了。如果有特殊要求可以用它
其餘的屬性都在下面了,看名字就知道是什麼意思啦~
實際運用
這個例子是將一個view進行移動和拉伸的動畫。涉及了多個屬性,當你要讓view選裝或者是移動的時候,請務必設定PivotX,PivotY來指定座標,如果不設定的話,移動和選擇默認的中心點都是Object的中心
public void startViewSimpleAnim(final View fromView,Rect finalBounds,int startOffsetY,int finalOffsetY, float startAlpha, float finalAlpha) { Rect startBounds = new Rect(); startBounds.set(Position.getGlobalVisibleRect(fromView)); //設置偏移量 startBounds.offset(0, -startOffsetY); finalBounds.offset(0, -finalOffsetY); //設定拉伸或者旋轉動畫的中心位置,這裏是相對於自身左上角 fromView.setPivotX(0f); fromView.setPivotY(0f); //計算拉伸比例 float scaleX = (float)finalBounds.width() / startBounds.width(); float scaleY = (float)finalBounds.height() / startBounds.height(); AnimatorSet set = new AnimatorSet(); ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(fromView, "alpha", startAlpha, finalAlpha); ObjectAnimator xAnim = ObjectAnimator.ofFloat(fromView, "x", startBounds.left, finalBounds.left); ObjectAnimator yAnim = ObjectAnimator.ofFloat(fromView, "y", startBounds.top, finalBounds.top); ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_X, 1f, scaleX); ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_Y,1f, scaleY); set.play(alphaAnim).with(xAnim).with(yAnim).with(scaleXAnim).with(scaleYAnim); set.setStartDelay(mStartDelay); set.setDuration(mAnimTime); set.setInterpolator(mInterpolator); set.addListener(new AnimListener(fromView,null)); set.start(); }
如果你操作對象的該屬性方法裏面,比如上例的setRotationX如果內部沒有調用view的重繪,則你需要自己按照下面方式手動調用。
anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // view.postInvalidate(); // view.invalidate(); } });
用xml文件來創建屬性動畫
大家肯定都清楚,View Animator 、Drawable Animator都可以在anim文件夾下創建動畫,然後在程序中使用,甚至在Theme中設置爲屬性值。當然了,屬性動畫其實也可以在文件中聲明。
首先在res下建立animator文件夾,然後建立res/animator/scalex.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>
然後通過動畫工具類就可以加載xml中的動畫文件了
AnimatorInflater.loadAnimator(this, R.anim.anim);
public void scaleX(View view) { // 加載動畫 Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex); anim.setTarget(mMv); anim.start(); }
縱向與橫向同時縮放
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together" > <objectAnimator android:duration="1000" android:propertyName="scaleX" android:valueFrom="1" android:valueTo="0.5" > </objectAnimator> <objectAnimator android:duration="1000" android:propertyName="scaleY" android:valueFrom="1" android:valueTo="0.5" > </objectAnimator> </set>
使用set標籤,有一個orderring屬性設置爲together,【還有另一個值:sequentially(表示一個接一個執行)】
六、通過AnimatorSet來控制多個動畫
AnimatorSet和AnimationSet類似,可以操作多個動畫屬性。AnimationSet提供了一個把多個動畫組合成一個組合的機制,並可設置組中動畫的時序關係,如同時播放,順序播放等。
例子01:
AnimatorSet bouncer = new AnimatorSet(); bouncer.play(anim1).before(anim2); bouncer.play(anim2).with(anim3); bouncer.play(anim2).with(anim4) bouncer.play(anim5).after(amin2); animatorSet.start();
- 播放anim1;
- 同時播放anim2,anim3,anim4;
- 播放anim5。
例子02:
animSet.playTogether(anim1, anim2);
animSet.start();
使用playTogether兩個動畫同時執行,當然還有playSequentially依次執行
例子03:
/** * anim1,anim2,anim3同時執行 * anim4接着執行 */ AnimatorSet animSet = new AnimatorSet(); animSet.play(anim1).with(anim2); animSet.play(anim2).with(anim3); animSet.play(anim4).after(anim3); animSet.setDuration(1000); animSet.start();
如果我們有一堆動畫,如何使用代碼控制順序,比如1,2同時;3在2後面;4在1之前
注意:animSet.play().with();也是支持鏈式編程的,但是不要想太多。
比如:animSet.play(anim1).with(anim2).before(anim3).before(anim5);
這樣是不行的,系統不會根據你寫的這一長串來決定先後的順序,所以麻煩你按照上面例子的寫法,多寫幾行
七、ViewPropertyAnimator
如果需要對一個View的多個屬性進行動畫可以用ViewPropertyAnimator類,該類對多屬性動畫進行了優化,會合並一些invalidate()來減少刷新視圖,該類在3.1中引入。
例子01:
以下兩段代碼實現同樣的效果
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
myView.animate().x(50f).y(100f);
例子02:
在SDK12的時候,給View添加了animate方法,更加方便的實現動畫效果。
btn.animate().x(200).y(200).alpha(0f); 就可以實現動畫效果了~
例子03:
簡單的使用mBlueBall.animate().alpha(0).y(mScreenHeight / 2).setDuration(1000).start()就能實現動畫~~不過需要SDK11,此後在SDK12,SDK16又分別添加了withStartAction和withEndAction用於在動畫前,和動畫後執行一些操作。當然也可以.setListener(listener)等操作。
package com.example.zhy_property_animation; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.Activity; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.widget.ImageView; public class ViewAnimateActivity extends Activity { protected static final String TAG = "ViewAnimateActivity"; private ImageView mBlueBall; private float mScreenHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.view_animator); DisplayMetrics outMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(outMetrics); mScreenHeight = outMetrics.heightPixels; mBlueBall = (ImageView) findViewById(R.id.id_ball); } public void viewAnim(View view) { // need API12 mBlueBall.animate()// .alpha(0)// .y(mScreenHeight / 2).setDuration(1000) // need API 12 .withStartAction(new Runnable() { @Override public void run() { Log.e(TAG, "START"); } // need API 16 }).withEndAction(new Runnable() { @Override public void run() { Log.e(TAG, "END"); runOnUiThread(new Runnable() { @Override public void run() { mBlueBall.setY(0); mBlueBall.setAlpha(1.0f); } }); } }).start(); } }
八、TypeEvalutors<T>
根據屬性的開始、結束值與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參數,通過上述FloatEvaluator應該很好看出其意思。
九、當Layout改變時應用動畫
ViewGroup中的子元素可以通過setVisibility使其Visible、Invisible或Gone,當有子元素可見性改變時,可以向其應用動畫。
通過LayoutTransition類的常量(第一個參數)可以區分動畫的類型,第二個參數爲Animator。
- APPEARING 當一個元素變爲Visible時對其應用的動畫
- CHANGE_APPEARING 當一個元素變爲Visible時,因系統要重新佈局有一些元素需要移動,這些要移動的元素應用的動畫
- DISAPPEARING 當一個元素變爲InVisible時對其應用的動畫
- CHANGE_DISAPPEARING 當一個元素變爲Gone時,因系統要重新佈局有一些元素需要移動,這些要移動的元素應用的動畫 disappearing from the container.
mTransition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);
更多的解釋
- 過渡的類型一共有四種:
- 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。
詳細的例子,請參考:http://blog.csdn.net/lmj623565791/article/details/38092093
參考博文如下:
http://www.open-open.com/lib/view/open1329994048671.html
http://www.tuicool.com/articles/yeM3my
http://blog.csdn.net/singwhatiwanna/article/details/17841165