android的md动画,Android-notes/动画体系详解.md at master · drs0214/Android-notes · GitHub
Android 動畫詳解:屬性動畫、View 動畫和幀動畫
在 Android 中,基本的動畫共有三種類型:
View 動畫:也叫視圖動畫或者補(bǔ)間動畫,主要是指 android.view.animation 包下面的一些類,只能被用來設(shè)置給 View,缺點(diǎn)是比如當(dāng)控件移動之后,接收點(diǎn)擊的控件的位置不會跟隨移動,并且能夠?qū)崿F(xiàn)的效果只有移動、縮放、旋轉(zhuǎn)和淡入淡出操作四種及其組合。
Drawable 動畫:也叫 Frame 動畫或者幀動畫,其實可以劃分到視圖動畫的類別,實現(xiàn)方式是將一些列的 Drawable 像幻燈片一樣一個一個地顯示。
Property 動畫: 屬性動畫主要是指 android.animation 包下面的一些類,只對 API 11 以上版本的Android 系統(tǒng)才有效,但我們可以通過兼容庫做低版本兼容。這種動畫可以設(shè)置給任何 Object,包括那些還沒有渲染到屏幕上的對象。這種動畫是可擴(kuò)展的,可以讓你自定義任何類型和屬性的動畫。
1、Drawable 動畫
在這里,我們先對 Drawable 動畫進(jìn)行講解,因為它相對于后面的兩種動畫比較簡單。在示例程序我們準(zhǔn)備了一系列圖片資源,并在 drawable 文件夾下面定義了動畫資源 record_anim.xml:
xmlns:android="http://schemas.android.com/apk/res/android">
然后,我們在代碼中使用該資源,并將其賦值給 ImageView。然后,我們從該控件中獲取該 Drawable 并將其轉(zhuǎn)換成 AnimationDrawable,隨后我們調(diào)用它的 start() 方法就開啟了 Drawable 動畫:
getBinding().ivRecord.setImageResource(R.drawable.record_anim);
animDraw = (AnimationDrawable) getBinding().ivRecord.getDrawable();
animDraw.start();
此外,我們可以調(diào)用該 Drawable 的 stop() 方法停止動畫。
幀動畫的注意事項
使用幀動畫的時候要注意設(shè)置的圖片不宜過多、過大,以防止因為內(nèi)存不夠而出現(xiàn) OOM。
2、View 動畫
2.1 基本 View 動畫
該動畫的資源處在 android.view.animation 包下,主要有以下幾個類,它們都繼承自 Animation ,我們可以使用它們來實現(xiàn)復(fù)雜的動畫。這些動畫類分別有對應(yīng)的 xml 標(biāo)簽,所以,我們可以在 xml 中定義動畫,也可以在代碼中實現(xiàn)動畫效果。這里的 AnimationSet 可以用來將多個動畫效果進(jìn)行組合,各預(yù)定義動畫的對照可以參考下面這張圖表:
2.2 View 動畫屬性
當(dāng)然,要實現(xiàn)一種動畫效果會有許多屬性需要指定,在 xml 中,我們用標(biāo)簽的屬性指定,在代碼中我們用對象的 setter 方法指定。于是,我們可以得到下面這個對應(yīng)關(guān)系:
上面的對應(yīng)關(guān)系是所有的 View 動畫共用的,對各個具體的動畫類型還有其獨(dú)有的屬性。你可以在各個動畫的構(gòu)造方法中,通過它們從 AttributeSet 中獲取了哪些字段來了解它們都定義了哪些屬性,這里我們不對其一一進(jìn)行說明。各預(yù)定義的屬性動畫分別按照不同的方式實現(xiàn)了 Animation 的 applyTransformation 方法,具體的這些屬性如何使用以及 View 動畫的效果是如何實現(xiàn)的,都可通過閱讀該方法的定義得知。
對于 AnimationSet,它內(nèi)部維護(hù)了一個 Animation 列表,并且其本身也是一個 Animation,所以,AnimationSet 內(nèi)部可以添加子 AnimationSet。
2.3 插值器
上文中我們提到過,View 動畫的具體實現(xiàn)是通過覆寫 Animation 的 applyTransformation 方法來完成的。這里我們以 AlphaAnimation 為例來看它是如何作用的,同時你應(yīng)該注意插值器的作用原理。該方法會在 Animation 中被循環(huán)調(diào)用,調(diào)用的時候會根據(jù)插值器計算出一個時間,并將其傳遞到 applyTransformation 方法中。
Animation 的 getTransformation 方法片段:
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
AlphaAnimation 的 applyTransformation 方法:
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
顯然,這里的 interpolatedTime 的是一個比例。比如,假如一個透明動畫需要持續(xù) 10s,透明度需要從 0.5f 到 1.0f,而插值的規(guī)則是一個二次函數(shù)。那么第 t (0
alpha = 0.5f + (1.0f - 0.5f) * t^2 / 100
以上就是插值器的作用原理,你也可以按照自己的需求實現(xiàn)自己的插值器,從而實現(xiàn)期待的動畫效果。
2.4 使用 View 動畫
作為一個例子,這里我們實現(xiàn)一個讓控件抖動的動畫。在 anim 文件夾下面,我們定義一個平移的動畫,并使用插值器使其重復(fù):
anim/shake.xml 的定義:
android:duration="700"
android:fromXDelta="0.0"
android:interpolator="@anim/cycle_7"
android:toXDelta="15.0" />
插值器 anim/cicle_7.xml 的定義:
android:cycles="4.0" />
然后,我們在代碼中加載 Animation 并調(diào)用控件的 startAnimation() 方法開啟動畫:
getBinding().v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.shake));
對于 View ,我們有 startAnimation() 用來對 View 開始動畫;有 clearAnimation() 用來取消 View 在執(zhí)行的動畫。
不使用 xml,僅使用代碼我們一樣可以實現(xiàn)上述的效果,這里我們不再進(jìn)行說明。
2.5 View 動畫的特殊使用場景
2.5.1 LayoutAnimation
LayoutAnimation 作用于 ViewGroup,可以使其子元素出場時都均有某種動畫效果,通常用于 ListView。我們可以像下面這樣定義布局動畫:
android:delay="500"
android:animation="@anim/shake"
xmlns:android="http://schemas.android.com/apk/res/android" />
顯然,這里我們需要引用一個其他的動畫。然后,我們可以在 ListView 的 layoutAnimation 屬性中指定布局動畫。或者調(diào)用 ListView 的 setLayoutAnimation() 方法應(yīng)用上述動畫。
2.5.2 Activity 切換
我們可以通過在 Activity 中調(diào)用 overridePendingTransition(R.anim.shake, R.anim.shake); 方法來重寫 Activity 的切換動畫。注意這個方法應(yīng)該在 startActivity(Intent) 或者 finish() 之后立即調(diào)用。
3、屬性動畫
3.1 基礎(chǔ)梳理
我們可以對比 View 動畫來學(xué)習(xí)屬性動畫。
屬性動畫主要是指 android.animation 包下面的一些類。
屬性動畫基礎(chǔ)的動畫類是 Animator;屬性動畫也為我們提供了幾個預(yù)定義的類:AnimatorSet, ObjectAnimator, TimeAnimator 和 ValueAnimator;這幾個預(yù)定義類之間的繼承關(guān)系是,AnimatorSet 和 ValueAnimator 直接繼承自 Animator,而 ObjectAnimator 和 TimeAnimator 繼承自 ValueAnimator。
與 View 動畫不同的是,屬性動畫的使用范圍更加寬泛,它不局限于 View,本質(zhì)上它是通過修改控件的屬性值實現(xiàn)的動畫。當(dāng)你嘗試對某個對象的某個屬性進(jìn)行修改的時候會有一些限制,即屬性動畫要求該對象必須提供了該屬性的 setter 方法。
屬性動畫也有 xml 和代碼兩種定義方式,它的 xml 通常定義在 animator 文件夾下面,而 View 動畫定義在 anim 文件夾下面。
屬性動畫提供了類似于 AnimationUtils 的方法用來從布局文件夾中加載屬性動畫:AnimatorInflater 類的 loadAnimator() 方法。
屬性動畫也有自己的插值器:TimeInterpolator,并且也提供了幾個預(yù)定義的插值器。
我們也可以調(diào)用 View 的方法來使用屬性動畫,我們可以通過 View 的 animate() 方法獲取一個 ViewPropertyAnimator,然后調(diào)用 ViewPropertyAnimator 的其他方法進(jìn)行鏈?zhǔn)秸{(diào)用以實現(xiàn)復(fù)雜的屬性動畫效果。
下面是屬性動畫的代碼實現(xiàn)和 xml 實現(xiàn)兩種方式的對比:
上文中,我們總結(jié)了屬性動畫的一些知識,并將其與 View 動畫進(jìn)行了對比。這里是一個簡單的梳理,在下文中我們會對屬性動畫進(jìn)行更加詳細(xì)的介紹。
3.2 使用屬性動畫
3.2.1 ValueAnimator
上面說過 ValueAnimator 是 ObjectAnimator 和 TimeAnimator 的基類,我們可以這樣使用它:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
這里我們使用 log 輸出了值漸變的過程,從日志中可以看出它的效果是值從 0 不斷遞增直到 1。如果我們在這個監(jiān)聽方法中根據(jù)值修改控件的屬性一樣可以實現(xiàn)動畫效果。除了 ofFloat() 還有 ofInt() 等方法,它們的效果相似。
3.2.2 ObjectAnimator
上面,如果我們想要實現(xiàn)動畫效果,需要在 ValueAnimator 的監(jiān)聽事件中修改對象的屬性,這里的 ObjectAnimator ,我們只需要傳入對象實例和屬性的字符串名稱,修改對象屬性的操作就可以自動完成。比如下面的程序的效果是控件 textview 的透明度會在 5s 之內(nèi)從 1 變成 0 再變回 1.
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
注意這里我們傳入的是 alpha,這個字段本身并不存在于控件中,而是有一個 setAlpha() 的方法。也就是說,ObjectAnimator 作用的原理是通過反射觸發(fā) setter 方法而不是修改屬性來實現(xiàn)的。你可以在類 PropertyValuesHolder 中更詳細(xì)地了解這方面的內(nèi)容。
PropertyValuesHolder 包裝了我們要修改的屬性的對象和方法等信息,然后會使用反射觸發(fā)指定對象的方法來完成對對象屬性的修改。其中
void setupSetter(Class targetClass) {
Class> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
}
會去尋找我們要修改屬性的 setter 方法,然后
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
會去觸發(fā) setter 方法,以修改對象的屬性。
3.2.3 AnimatorSet
AnimatorSet 內(nèi)部提供了一個構(gòu)建者 AnimatorSet.Builder 來幫助我們構(gòu)建組合動畫,AnimatorSet.Builder 提供了下面四種方法:
after(Animator anim):將現(xiàn)有動畫插入到傳入的動畫之后執(zhí)行
after(long delay):將現(xiàn)有動畫延遲指定毫秒后執(zhí)行
before(Animator anim):將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行
with(Animator anim):將現(xiàn)有動畫和傳入的動畫同時執(zhí)行
當(dāng)我們調(diào)用 AnimatorSet 的 play() 方法的時候就能獲取一個 AnimatorSet.Builder 實例,然后我們就可以使用構(gòu)建者的方法進(jìn)行鏈?zhǔn)秸{(diào)用了:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
3.2.4 TypeEvaluator
正如前文所述,屬性動畫也有自己的插值器,我們可以通過插值函數(shù)指定在某個時間段內(nèi)屬性改變的速率。插值函數(shù)得到的是一個比例,是沒有意義的。在 View 動畫的 AlphaAnimation 中,如果我們指定了起止的透明度,那么我們可以通過透明度的計算規(guī)則得到某個時刻的透明度。但是對于屬性動畫,因為它可以應(yīng)用于任何屬性,這個屬性又可能是任何類型的,那么這個屬性將采用什么樣的計算規(guī)則呢?這就需要我們使用 TypeEvaluator 來指定一個計算規(guī)則。也就是說,TypeEvaluator 是屬性動畫的屬性的計算規(guī)則。
下面是 TypeEvaluator 的定義,這里的三個參數(shù)的含義分別是,fraction 是當(dāng)前的比例,可以通過插值器計算得到;startValue 和 endValue 分別是屬性變化的起止值。它的返回結(jié)果就是在某個時刻某個屬性的值。
public interface TypeEvaluator {
public T evaluate(float fraction, T startValue, T endValue);
}
屬性動畫中已經(jīng)為我們提供了幾個預(yù)定義的 TypeEvaluator,比如 FloatEvaluator:
public class FloatEvaluator implements TypeEvaluator {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
在屬性動畫的 PropertyValuesHolder 中會根據(jù)屬性的類型選擇預(yù)定義的 TypeEvaluator。但是如果我們的屬性的類型不在預(yù)定義的范圍之內(nèi)就需要自己實現(xiàn)一個 TypeEvaluator。下面我們以日期類型為例來實現(xiàn)一個 TypeEvaluator。
當(dāng)我們使用 ValueAnimator 的 ofObject() 方法獲取 ValueAnimator 實例的時候,要求我們傳入一個 TypeEvaluator,于是我們可以像下面這樣定義:
private static class DateEvaluator implements TypeEvaluator {
@Override
public Date evaluate(float fraction, Date startValue, Date endValue) {
long startTime = startValue.getTime();
return new Date((long) (startTime + fraction * (endValue.getTime() - startTime)));
}
}
然后,我們可以這樣使用它:
ValueAnimator animator = ValueAnimator.ofObject(new DateEvaluator(), new Date(0), new Date());
animator.setDuration(5000);
animator.addUpdateListener(animation -> {
Date date = (Date) animation.getAnimatedValue();
LogUtils.d(date);
});
animator.start();
這樣就可以得到在 5s 之內(nèi)輸出的從時間戳為0,到當(dāng)前時刻的所有的日期變化。
3.2.5 TimeInterpolator
就像 View 動畫一樣,我們可以為屬性動畫指定一個插值器。插值器的作用是用來設(shè)置指定時間段內(nèi)數(shù)值的變化的速率。在屬性動畫中,插值器是 TimeInterpolator,同樣也有幾個默認(rèn)的實現(xiàn):
AccelerateDecelerateInterolator:先加速后減速。
AccelerateInterpolator:加速。
DecelerateInterpolator:減速。
AnticipateInterpolator:先向相反方向改變一段再加速播放。
AnticipateOvershootInterpolator:先向相反方向改變,再加速播放,會超出目標(biāo)值然后緩慢移動至目標(biāo)值,類似于彈簧回彈。
BounceInterpolator:快到目標(biāo)值時值會跳躍。
CycleIinterpolator:動畫循環(huán)一定次數(shù),值的改變?yōu)橐徽液瘮?shù):Math.sin(2 * mCycles * Math.PI * input)。
LinearInterpolator:線性均勻改變。
OvershottInterpolator:最后超出目標(biāo)值然后緩慢改變到目標(biāo)值。
3.2.6 在 xml 中使用屬性動畫
我們可以像下面這樣定義一個屬性動畫,
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
...
這里的android:ordering用于控制子動畫啟動方式是先后有序的還是同時進(jìn)行,兩個可選參數(shù): sequentially 表示動畫按照先后順序;together(默認(rèn))表示動畫同時啟動。
這里的 標(biāo)簽的含義如下:
這樣在 XML 中定義了屬性動畫之后,我們可以在代碼中通過工具類獲取到動畫實例并使用:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animtor.property_animator);
set.setTarget(myObject);
set.start();
4、使用動畫的注意事項
內(nèi)存耗盡:使用幀動畫的時候防止因為圖片過多導(dǎo)致 OOM。
View 動畫并沒有真正改變 View 的位置:View 動畫并沒有真正改變 View 的屬性,即 View 動畫執(zhí)行之后并未改變 View 的真實布局屬性值。譬如我們在布局中有一個 Button 在屏幕上方,我們設(shè)置了平移動畫移動到屏幕下方然后保持動畫最后執(zhí)行狀態(tài)呆在屏幕下方,這時如果點(diǎn)擊屏幕下方動畫執(zhí)行之后的 Button 是沒有任何反應(yīng)的,而點(diǎn)擊原來屏幕上方?jīng)]有 Button 的地方卻響應(yīng)的是點(diǎn)擊 Button 的事件。
內(nèi)存泄漏:使用屬性動畫的時候,當(dāng)使用無限循環(huán)動畫,需要在 Activity 退出的時候停止動畫,不然可能會因為無法釋放資源而導(dǎo)致 Activity 內(nèi)存泄漏。
動畫兼容:當(dāng) APP 需要兼容到 API 11 以下的時候就需要注意動畫的兼容問題。
使用 dp 而不是 px:因為 px 在不同設(shè)備上面的兼容問題,使用動畫的時候盡量使用 dp 作為單位。
硬件加速:使用硬件加速可以提升動畫的流暢性。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的android的md动画,Android-notes/动画体系详解.md at master · drs0214/Android-notes · GitHub的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言用循环写出新年祝福语图案,非常漂亮
- 下一篇: android 5.0 新功能,Andr