本篇為對android動畫機(jī)制的粗略總結(jié),目的是方便查詢而非講解,所以代碼部分的注釋比較多,但是很多該配圖的地方?jīng)]有配。
安卓的動畫是一種比較基本的特效,項目中經(jīng)常會用到
安卓的動畫分為三種,分別是View動畫,幀動畫,屬性動畫。
View動畫
View動畫的重點是動畫,是視圖(View)層面上的一種變換,操作的重點在視圖上,這與屬性動畫是完全不同的。
View動畫,也有叫補(bǔ)間動畫的,就是Flash中的那個補(bǔ)間動畫,給定初始值跟結(jié)束值,自動計算出過程的一種動畫形式。
我個人比較喜歡的用法是用XML文件寫動畫,然后用代碼加載使用,這樣的動畫可讀性非常好。
view動畫的所屬文件夾為res/anim文件夾
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:interpolator="@android:anim/cycle_interpolator"
android:shareInterpolator="false">
<!--
duration 代表動畫執(zhí)行時間
若<set>與其子項目都設(shè)置有 duration 屬性,則以<set duration>為準(zhǔn),其子項目屬性會被覆蓋
如下<alpha android:duration="5000" >會被覆蓋
interpolator 代表動畫插值器
shareInterpolator 代表整個set是否共享插值器,默認(rèn)為true
若<set>與其子項目都設(shè)置有 interpolator 屬性,且shareInterpolator 設(shè)置為true,則以<set interpolator >為準(zhǔn),其子項目屬性會被覆蓋
若shareInterpolator 設(shè)置為false,則以其子項目屬性為準(zhǔn)
如下<alpha android:interpolator="@android:anim/linear_interpolator">會生效
但其余子項目沒有設(shè)置插值器,所以默認(rèn)為<set android:interpolator="@android:anim/cycle_interpolator">
-->
<alpha
android:duration="5000"
android:fromAlpha="1"
android:interpolator="@android:anim/linear_interpolator"
android:toAlpha="0" />
<!--
alpha 透明度變換,范圍為[0,1],沒有單位
fromAlpha 透明度變換的起始值
toAlpha 透明變換的結(jié)束值
-->
<translate
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="100%" />
<!--
translate 平移變換,可填入浮點型數(shù)字,無上限。或者填入百分?jǐn)?shù)。我比較傾向于填寫百分?jǐn)?shù),比較直觀。
fromXDelta,fromXDelta 平移變換的起始位置,0%為自身的位置
toXDelta,toYDelta 平移變換的結(jié)束位置,100%為當(dāng)前View的寬高
-->
<rotate
android:fromDegrees="0"
android:pivotX="10"
android:pivotY="10"
android:toDegrees="180" />
<!--
rotate 旋轉(zhuǎn)變換,一般填入整型,代表旋轉(zhuǎn)角度
fromDegrees 旋轉(zhuǎn)變換起始角度
toDegrees 旋轉(zhuǎn)變換結(jié)束角度
pivotX 軸點,代表變換的中心位置,此時為旋轉(zhuǎn)圓心的X值
pivotY 軸點,代表變換的中心位置,此時為旋轉(zhuǎn)圓心的Y值
-->
<scale
android:fromXScale="0%"
android:fromYScale="0%"
android:pivotX="10"
android:pivotY="10"
android:toXScale="100%"
android:toYScale="100%" />
<!--
scale 縮放變換,一般填入整型,單位使用dp之類的。或者填入百分?jǐn)?shù)。我比較傾向于填寫百分?jǐn)?shù),比較直觀。
fromXScale,fromYScale 縮放變換的起始值
toXScale,toYScale 縮放變換的結(jié)束值
pivotX 軸點,代表變換的中心位置,此時為縮放中心的X值
pivotY 軸點,代表變換的中心位置,此時為縮放中心的Y值
-->
<set android:duration="4000">
<!--
外層set設(shè)置的屬性,內(nèi)層set都可以設(shè)置,重復(fù)時,覆蓋規(guī)則與其他子項目相同
-->
</set>
</set>
使用的時候加載動畫并且執(zhí)行就好,非常簡單。
Animation anim = AnimationUtils.loadAnimation(this, R.anim.anim_view_anim);
View.startAnimation(anim);
【此處假裝有動圖】
有時候有的屬性沒有自動提示,敲出不報錯就沒問題,
例如android:interpolator="@android:anim/cycle_interpolator"有時候編譯器不會提示
插值器,估值器的概念,下文會說。
實現(xiàn)一個動畫,除了使用xml意外,還可以用代碼來寫,當(dāng)動畫的某些參數(shù)需要動態(tài)調(diào)整的時候需要這么使用。但寫起來比較繁瑣,看起來比較亂,所以就只放一個例子,使用方法相同。
public void translate(View v) {
// 創(chuàng)建位移補(bǔ)間動畫
// ta = new TranslateAnimation(-100, 100, -50, 50);
ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -2,
Animation.RELATIVE_TO_SELF, 3, Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0);
// 指定動畫持續(xù)時間
ta.setDuration(2000);
// 設(shè)置重復(fù)播放的次數(shù)
ta.setRepeatCount(1);
// 設(shè)置重復(fù)播放的模式
ta.setRepeatMode(Animation.REVERSE);
// 填充動畫在結(jié)束的位置上
ta.setFillAfter(true);
// 播放動畫
iv.startAnimation(ta);
}
View動畫也可以自定義,通過繼承Animation就可以實現(xiàn),只不過由于動畫實質(zhì)上是一種數(shù)學(xué)變換過程,實現(xiàn)起來比較麻煩。項目中系統(tǒng)自帶的幾種動畫組合就能解決大部分問題,所以自定義View動畫不常用。
View動畫的其他用法
View動畫還可以用于設(shè)定子元素的出場效果,以及Activity的切換效果。
LayoutAnimation
LayoutAnimation作用于ViewGroup,為其制定一個動畫,當(dāng)它的子元素初始化時,會帶有這個效果,常用在RecycleView上。
首先,我們需要一個layoutAnimation
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/rv_layout_anim_item"
android:animationOrder="normal"
android:delay="10%" />
<!--
android:animation 為子元素指定入場動畫
android:animationOrder 表示動畫的順序,normal表示順序顯示,reverse表示逆向顯示,后面的先入場,random表示隨機(jī)
android:delay 表示子元素相對于上一個入場的元素的延遲時間,比如一個動畫100ms,50%的延遲則表明下一個元素延遲50ms
-->
然后我們需要一個視圖動畫
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:fromYDelta="-20%"
android:toYDelta="0" />
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
</set>
最后我們?yōu)镽ecycleView指定這個入場動畫
<android.support.v7.widget.RecyclerView
...
android:layoutAnimation="@anim/rv_layout_anim" />
入場動畫也可以為其他ViewGroup指定,效果略有差異。
Activity的切換效果
overridePendingTransition(R.anim.anim1, R.anim.anim2);
// anim1表示入場的Activity執(zhí)行的動畫
// anim2表示退場的Activity執(zhí)行的動畫
需要注意的是,這個動畫需要放到startActivity或者finish語句的后面,才會生效。
幀動畫
幀動畫就是按照既定順序去播放一組圖片的動畫效果,類似于電影膠片那種。
幀動畫比較簡單,效果也比較直觀,但是缺點也是顯而易見的,過多過大的圖片會急劇增大app體積,操作不當(dāng)還很容易OOM。
適用范圍一般,但是很好用的一種動畫效果,比如在下拉刷新時,表頭部分的動態(tài)小圖片。
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@mipmap/ic_money_off"
android:duration="500" />
<item
android:drawable="@mipmap/ic_money_on"
android:duration="500" />
</animation-list>
幀動畫資源文件存在于drawable文件夾中,使用時候,獲取對象并播放即可。
btn_anim.setBackgroundResource(R.drawable.anim_frame);
final AnimationDrawable drawable = (AnimationDrawable) btn_anim.getBackground();
btn_anim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawable.start();
}
});
可以在代碼中設(shè)置背景,也可以在XML中設(shè)置。
屬性動畫
屬性動畫,理解為在一個時間間隔內(nèi),完成對象從一個屬性值到另一個屬性值的轉(zhuǎn)變。
屬性動畫的重點在屬性,是對對象的屬性的一種變換,操作重點在于數(shù)值變換,這與View動畫是完全不同的。
在看屬性動畫之前,請先暫時忘記View動畫以及幀動畫,因為他們不是一個層面上的東西。不能慣性的將屬性動畫理解為,對View動畫的擴(kuò)展,這樣理解出來的屬性動畫是非常局限的,因為他們雖然同為動畫,但是實現(xiàn)原理是完全不同的,使用屬性動畫可以做到很多View動畫做不到的有趣的特效。
屬性動畫曾經(jīng)有著嚴(yán)重的兼容問題,但現(xiàn)在app一般要求API16起步,這個問題早已不復(fù)存在。
屬性動畫也可以選用xml的方式或者代碼的方式來實現(xiàn),使用場景類似于View動畫。
屬性動畫的所屬文件夾為res/animator文件夾,注意,這與View動畫不是同一個文件夾,并且anim文件夾也不是animator的簡寫。
以下為xml構(gòu)建屬性動畫時,可能會用到的屬性。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together | sequentially">
<!--
ordering together 表示動畫同時播放 sequentially 表示動畫依次播放
默認(rèn)值是together
-->
<objectAnimator
android:duration="int"
android:propertyName="string"
android:repeatCount="int"
android:repeatMode="restart | reverse"
android:startOffset="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:valueType="colorType | intType | floatType" />
<!--
duration 表示動畫時長
repeatCount 表示動畫重復(fù)次數(shù)
repeatMode 表示動畫的重復(fù)模式,restart表示聯(lián)系重復(fù)播放,reverse表示一次正著播放,一次倒著
startOffset 表示動畫的延遲時間
valueFrom 表示屬性的起始值,允許的數(shù)據(jù)類型為float,int,color
valueTo 表示屬性的結(jié)束值,允許的數(shù)據(jù)類型為float,int,color
propertyName 指定做變換的屬性名
valueType 制定做變換的屬性的值的類型,允許的類型為colorType,intType,floatType,如果是color,則該屬性不需要指定
需要注意的是,如果propertyName 與valueType類型不匹配,可能會出現(xiàn)動畫無法執(zhí)行的情況。
-->
<animator
android:duration="int"
android:repeatCount="int"
android:repeatMode="restart | reverse"
android:startOffset="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:valueType="colorType | intType | floatType" />
<!--
animator 與 objectAnimator的區(qū)別僅僅是缺少propertyName屬性
-->
<set>
...
</set>
</set>
可以看到,屬性動畫有兩個標(biāo)簽
<objectAnimator/>代表ObjectAnimator
<animator/>代表ValueAnimator
ObjectAnimator與ValueAnimator的關(guān)系是繼承的關(guān)系。
ObjectAnimator類作為ValueAnimator的子類,封裝了一些方便開發(fā)人員的方法,簡化了屬性動畫的實現(xiàn)。
ObjectAnimator類一般用于實現(xiàn)一些常見的動畫效果,例如旋轉(zhuǎn)評議縮放透明度等,ValueAnimator通過ValueAnimator.AnimatorUpdateListener接口,以及估值器,插值器,可以實現(xiàn)一些更加自由的效果。
ObjectAnimator
使用xml構(gòu)建一個屬性動畫的步驟與View動畫的步驟相似,以下是屬性動畫的xml文件。
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:propertyName="x"
android:valueTo="200"
android:valueType="floatType" />
---
Animator animator = AnimatorInflater.loadAnimator(mContext, R.animator.anim_object_anim);
//用objectAnimator也可以只不過需要強(qiáng)轉(zhuǎn)
animator.setTarget(tv_text);
animator.start();
或者是使用set集合的方式。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="300"
android:propertyName="x"
android:valueTo="200"
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:propertyName="y"
android:valueTo="200"
android:valueType="floatType" />
</set>
---
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(mContext, R.animator.anim_object_anim);
set.setTarget(tv_text);
set.start();
再次強(qiáng)調(diào),如果xml文件中,propertyName 與valueType類型不匹配,可能會出現(xiàn)動畫無法執(zhí)行的情況。
或者也可以用代碼的方式實現(xiàn)一個屬性動畫。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv_text, "x", 200);
objectAnimator.setDuration(3000);
objectAnimator.start();
ObjectAnimator中,可用的propertyName有以下幾個:
translationX 與 translationY,控制位移
x 與 y,控制位移
rotation、rotationX 與 rotationY,控制旋轉(zhuǎn)角度
scaleX 與 scaleY,控制縮放
pivotX 與 pivotY,控制以上旋轉(zhuǎn),縮放的中心點
alpha,控制透明度
以及詳細(xì)說明:
- translationX、translationY,x、y:
這四個屬性作為一組來說明,x,y,是View左上角的坐標(biāo),其中,x = left + translationX,y = right + translationY。left,right指的是做動畫的View自身的layout_marginLeft,layout_marginRight。
修改任何一個屬性都會使得控件位移,但是在移動細(xì)節(jié)上稍有差異。
實例代碼以及圖1:
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(ll_text1, "translationX", 0);
objectAnimator1.setDuration(3000);
objectAnimator1.start();
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(ll_text2, "x", 0);
objectAnimator2.setDuration(3000);
objectAnimator2.start();
可以看到,對控件的x屬性做動畫的話,會忽略其自身的margin值
-
rotation、rotationX、rotationY
這三個屬性作為一組來說明,他們可以控制旋轉(zhuǎn)角度
其中rotation控制著 2D 旋轉(zhuǎn)角度
rotationX,rotationY控制圍繞某樞軸點的 3D旋轉(zhuǎn)
由于不好描述,放一個圖,圖2
圖2 rotation與rotationX.png
其中,text1為rotation旋轉(zhuǎn),text2為rotationX旋轉(zhuǎn)。 scaleX、scaleY
這兩個屬性控制著 View 圍繞某樞軸點的 2D 縮放比例,這沒什么好說的,就是常規(guī)縮放。pivotX、pivotY
這兩個屬性控制著上文中,旋轉(zhuǎn)縮放的樞軸點的位置,缺省的樞軸點是控件的中心點alpha
這個屬性表示控件的透明度。缺省值為 1 (不透明),為 0 則表示完全透明(看不見)
插值器與估值器
在ValueAnimator之前,先要說明插值器與估值器的概念,插值器的概念由于比較常見,不做過多說明也不影響理解,但是估值器的概念不很常見,所以放在這個位置一并說明。
TimeInterpolator,譯作插值器,作用是根據(jù)時間流逝的百分比來計算出當(dāng)前值改變的百分比,系統(tǒng)預(yù)置有9個插值器,他們的效果如下:
需要注意的是,插值器的特性,使得動畫計算的終點與起點有機(jī)會相同,如使用CycleInterpolator插值器或者自定義三角函數(shù)差值器,當(dāng)然這也是一個很好用的特性,使用時請保持想象力。
自定義插值器需要繼承Interpolator接口,也很簡單
public class MyInterpolator implements Interpolator {
@Override
public float getInterpolation(float v) {
return (float) Math.sin(0.5 * Math.PI * v);
}
}
代碼中,return后面公式部分決定了使用該插值器后的運動軌跡,插值器的公式就是簡單的二元方程式。
這里提供一個插值器公式的工具網(wǎng)站,http://inloop.github.io/interpolator,是github上的一個項目,建議保存方便以后使用。
插值器使用時,可以直接寫在xml中,或者在代碼中設(shè)置
android:interpolator="@android:anim/decelerate_interpolator"
---
objectAnimator.setInterpolator(new LinearInterpolator());
接下來是估值器
Evaluator,譯作估值器,作用是可以用來對int,float,color以外的類型做動畫。需要繼承TypeEvaluator接口,然后通過該接口來自定義估值算法以及返回值。
以下代碼是一個對對象做動畫的例子
估值器代碼
public class MyTypeEvaluator implements TypeEvaluator<TwoNumberBean> {
@Override
public TwoNumberBean evaluate(float v, TwoNumberBean startBean, TwoNumberBean endBean) {
TwoNumberBean bean = new TwoNumberBean();
bean.number1 = (int) (startBean.number1 + v * (endBean.number1 - startBean.number1));
bean.number2 = (int) (startBean.number2 + v * (endBean.number2 - startBean.number2));
return bean;
}
}
其中,evaluate中,第一個參數(shù)表示估值小數(shù),例如總時長1000ms,現(xiàn)在運行了500ms,估值小數(shù)的值就是500/1000=0.5。第二、三個參數(shù)是開始值以及結(jié)束值。
以及使用該估值器的方法
ValueAnimator va = ValueAnimator.ofObject(new MyTypeEvaluator(), new TwoNumberBean(0, 0), new TwoNumberBean(100, 200));
va.setInterpolator(new AccelerateDecelerateInterpolator());
va.setDuration(5000);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
TwoNumberBean bean = (TwoNumberBean) valueAnimator.getAnimatedValue();
tv_text1.setText(bean.number1 + "," + bean.number2);
}
});
va.start();
以及運行效果
只是一個示例,估值器同樣需要想象力。
ValueAnimator
作為ObjectAnimator的父類,協(xié)同插值器,估值器,提供了更加多的可能性,當(dāng)然也更加復(fù)雜。
屬性動畫提供了兩個監(jiān)聽器,用于監(jiān)聽動畫的播放過程,主要有兩個接口
- AnimatorListener監(jiān)聽了動畫的開始,結(jié)束,取消,重復(fù)播放,四個狀態(tài),為了方便開發(fā),系統(tǒng)還提供了AnimatorListenerAdapter這個類,畢竟有時候用不到所有方法。
- AnimatorUpdateListener監(jiān)聽了整個動畫過程,每一幀都會調(diào)用一次該方法,比如上面那個例子。
對任意屬性做動畫
ValueAnimator 最靈活的一點是它可以對任意對象的任意屬性做動畫。
我們先看一個不是那么好的例子
public class LogBean {
int i;
public int getI() {
Log.e("LogBean: ", "getI:_" + i);
return i;
}
public void setI(int i) {
Log.e("LogBean: ", "setI:_" + i);
this.i = i;
}
}
---
// 對該對象做動畫
ObjectAnimator.ofInt(new LogBean(),"i",500).setDuration(5000).start();
然后去看log輸出,可以看到LogBean的get、set方法被調(diào)用。
記不記得上文說過,“不能慣性的將屬性動畫理解為,對View動畫的擴(kuò)展”,因為屬性動畫是以一種更加靈活的方式實現(xiàn)的動畫,我曾經(jīng)因為這個思維慣性,對屬性動畫的理解偏差很大。
通過這個例子,我們可以得出以下結(jié)論:
- 被做動畫的對象必須要有駝峰命名的get,set方法,否則程序會崩潰。(底層是通過反射實現(xiàn)的)
- 屬性動畫對對象的某個屬性值的改變一定要能通過某種方法展現(xiàn)出來,一般開發(fā)時,當(dāng)然是通過改變UI,而這個例子是通過log。
PS:如果展現(xiàn)不出來不就沒有意義了(~ ̄▽ ̄)~
當(dāng)然,在很多情況下,我們需要對一個對象做動畫時,往往不能滿足這兩個條件,官方文檔中有這么三種解決方案。
- 如果有權(quán)限的話,比如是自己寫的對象,可以給這個對象加上get、set方法。不過這個辦法一般來說沒用。
- 使用包裝類,為該對象添加get、set方法。
- 采用ValueAnimator ,并且通過接口監(jiān)聽動畫的整個過程,自己改變屬性。這是我最喜歡的一個辦法,畢竟靈活度非常高。上文中估值器的例子就是這個方法。
幾個需要注意的點
- 幀動畫的圖片不能太大,否則很容易OOM
- 如果屬性動畫設(shè)置為無限循環(huán),請在關(guān)閉當(dāng)前Activity時,務(wù)必關(guān)閉該動畫,否則會導(dǎo)致內(nèi)存泄漏,類似于Handler的內(nèi)存泄漏。但是View動畫并沒有這個問題。
- View動畫只是改變視圖,并不會真正改變View的狀態(tài)。
- View動畫有時會讓view.setVisibility(View.GONE)失效,只要調(diào)用view.clearAnimation()清除動畫即可。
- 可以的話,請開啟硬件加速。
個人理解,難免有錯誤紕漏,歡迎指正。轉(zhuǎn)載請注明出處。