在 Android 中,基本的動畫共有三種類型:android
android.view.animation
包下面的一些類,只能被用來設置給 View,缺點是好比當控件移動以後,接收點擊的控件的位置不會跟隨移動,而且可以實現的效果只有移動、縮放、旋轉和淡入淡出操做四種及其組合。android.animation
包下面的一些類,只對 API 11
以上版本的Android 系統纔有效,但咱們能夠經過兼容庫作低版本兼容。這種動畫能夠設置給任何 Object,包括那些尚未渲染到屏幕上的對象。這種動畫是可擴展的,可讓你自定義任何類型和屬性的動畫。文末有免費福利哦git
在這裏,咱們先對 Drawable 動畫進行講解,由於它相對於後面的兩種動畫比較簡單。在示例程序咱們準備了一系列圖片資源,並在 drawable 文件夾下面定義了動畫資源 record_anim.xml:github
<?xml version="1.0" encoding="utf-8"?> <animation-list android:oneshot="false" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/record0" android:duration="500"/> <item android:drawable="@drawable/record1" android:duration="500"/> <item android:drawable="@drawable/record2" android:duration="450"/> <item android:drawable="@drawable/record3" android:duration="400"/> <item android:drawable="@drawable/record4" android:duration="350"/> <item android:drawable="@drawable/record5" android:duration="400"/> <item android:drawable="@drawable/record6" android:duration="400"/> </animation-list> 複製代碼
而後,咱們在代碼中使用該資源,並將其賦值給 ImageView。而後,咱們從該控件中獲取該 Drawable 並將其轉換成 AnimationDrawable,隨後咱們調用它的 start()
方法就開啓了 Drawable 動畫:面試
getBinding().ivRecord.setImageResource(R.drawable.record_anim); animDraw = (AnimationDrawable) getBinding().ivRecord.getDrawable(); animDraw.start(); 複製代碼
此外,咱們能夠調用該 Drawable 的 stop()
方法中止動畫。小程序
使用幀動畫的時候要注意設置的圖片不宜過多、過大,以防止由於內存不夠而出現 OOM。性能優化
該動畫的資源處在 android.view.animation
包下,主要有如下幾個類,它們都繼承自 Animation
,咱們可使用它們來實現複雜的動畫。這些動畫類分別有對應的 xml 標籤,因此,咱們能夠在 xml 中定義動畫,也能夠在代碼中實現動畫效果。這裏的 AnimationSet
能夠用來將多個動畫效果進行組合,各預約義動畫的對照能夠參考下面這張圖表:架構
固然,要實現一種動畫效果會有許多屬性須要指定,在 xml 中,咱們用標籤的屬性指定,在代碼中咱們用對象的 setter 方法指定。因而,咱們能夠獲得下面這個對應關係:app
上面的對應關係是全部的 View 動畫共用的,對各個具體的動畫類型還有其獨有的屬性。你能夠在各個動畫的構造方法中,經過它們從 AttributeSet
中獲取了哪些字段來了解它們都定義了哪些屬性,這裏咱們不對其一一進行說明。各預約義的屬性動畫分別按照不一樣的方式實現了 Animation
的 applyTransformation
方法,具體的這些屬性如何使用以及 View 動畫的效果是如何實現的,均可經過閱讀該方法的定義得知。框架
對於 AnimationSet
,它內部維護了一個 Animation
列表,而且其自己也是一個 Animation
,因此,AnimationSet
內部能夠添加子 AnimationSet
。ide
文末有免費福利哦
上文中咱們提到過,View 動畫的具體實現是經過覆寫 Animation
的 applyTransformation
方法來完成的。這裏咱們以 AlphaAnimation
爲例來看它是如何做用的,同時你應該注意插值器的做用原理。該方法會在 Animation
中被循環調用,調用的時候會根據插值器計算出一個時間,並將其傳遞到 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
的是一個比例。好比,假如一個透明動畫須要持續 10s
,透明度須要從 0.5f
到 1.0f
,而插值的規則是一個二次函數。那麼第 t (0<t<10)
秒的時候控件的透明度應該是:
alpha = 0.5f + (1.0f - 0.5f) * t^2 / 100 複製代碼
以上就是插值器的做用原理,你也能夠按照本身的需求實現本身的插值器,從而實現期待的動畫效果。
做爲一個例子,這裏咱們實現一個讓控件抖動的動畫。在 anim 文件夾下面,咱們定義一個平移的動畫,並使用插值器使其重複:
anim/shake.xml
的定義:
<?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="700" android:fromXDelta="0.0" android:interpolator="@anim/cycle_7" android:toXDelta="15.0" /> 複製代碼
插值器 anim/cicle_7.xml
的定義:
<?xml version="1.0" encoding="utf-8"?> <cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android" android:cycles="4.0" /> 複製代碼
而後,咱們在代碼中加載 Animation
並調用控件的 startAnimation()
方法開啓動畫:
getBinding().v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.shake)); 複製代碼
對於 View
,咱們有 startAnimation()
用來對 View
開始動畫;有 clearAnimation()
用來取消 View
在執行的動畫。
不使用 xml,僅使用代碼咱們同樣能夠實現上述的效果,這裏咱們再也不進行說明。
LayoutAnimation
做用於 ViewGroup
,可使其子元素出場時都均有某種動畫效果,一般用於 ListView
。咱們能夠像下面這樣定義佈局動畫:
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation android:delay="500" android:animation="@anim/shake" xmlns:android="http://schemas.android.com/apk/res/android" /> 複製代碼
顯然,這裏咱們須要引用一個其餘的動畫。而後,咱們能夠在 ListView
的 layoutAnimation
屬性中指定佈局動畫。或者調用 ListView
的 setLayoutAnimation()
方法應用上述動畫。
咱們能夠經過在 Activity
中調用 overridePendingTransition(R.anim.shake, R.anim.shake);
方法來重寫 Activity
的切換動畫。注意這個方法應該在 startActivity(Intent)
或者 finish()
以後當即調用。
咱們能夠對比 View 動畫來學習屬性動畫。
android.animation
包下面的一些類。Animator
;屬性動畫也爲咱們提供了幾個預約義的類:AnimatorSet
, ObjectAnimator
, TimeAnimator
和 ValueAnimator
;這幾個預約義類之間的繼承關係是,AnimatorSet
和 ValueAnimator
直接繼承自 Animator
,而 ObjectAnimator
和 TimeAnimator
繼承自 ValueAnimator
。setter
方法。xml
和代碼兩種定義方式,它的 xml
一般定義在 animator
文件夾下面,而 View 動畫定義在 anim
文件夾下面。AnimationUtils
的方法用來從佈局文件夾中加載屬性動畫:AnimatorInflater
類的 loadAnimator()
方法。TimeInterpolator
,而且也提供了幾個預約義的插值器。View
的方法來使用屬性動畫,咱們能夠經過 View 的 animate()
方法獲取一個 ViewPropertyAnimator
,而後調用 ViewPropertyAnimator
的其餘方法進行鏈式調用以實現複雜的屬性動畫效果。下面是屬性動畫的代碼實現和 xml
實現兩種方式的對比:
上文中,咱們總結了屬性動畫的一些知識,並將其與 View 動畫進行了對比。這裏是一個簡單的梳理,在下文中咱們會對屬性動畫進行更加詳細的介紹。
上面說過 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
。若是咱們在這個監聽方法中根據值修改控件的屬性同樣能夠實現動畫效果。除了 ofFloat()
還有 ofInt()
等方法,它們的效果類似。
上面,若是咱們想要實現動畫效果,須要在 ValueAnimator
的監聽事件中修改對象的屬性,這裏的 ObjectAnimator
,咱們只須要傳入對象實例和屬性的字符串名稱,修改對象屬性的操做就能夠自動完成。好比下面的程序的效果是控件 textview
的透明度會在 5s
以內從 1 變成 0 再變回 1.
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f); animator.setDuration(5000); animator.start(); 複製代碼
注意這裏咱們傳入的是 alpha
,這個字段自己並不存在於控件中,而是有一個 setAlpha()
的方法。也就是說,ObjectAnimator
做用的原理是經過反射觸發 setter
方法而不是修改屬性來實現的。你能夠在類 PropertyValuesHolder
中更詳細地瞭解這方面的內容。
PropertyValuesHolder
包裝了咱們要修改的屬性的對象和方法等信息,而後會使用反射觸發指定對象的方法來完成對對象屬性的修改。其中
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()); } } } 複製代碼
會去觸發 setter
方法,以修改對象的屬性。
文末有免費福利哦
AnimatorSet
內部提供了一個構建者 AnimatorSet.Builder
來幫助咱們構建組合動畫,AnimatorSet.Builder
提供了下面四種方法:
after(Animator anim)
:將現有動畫插入到傳入的動畫以後執行after(long delay)
:將現有動畫延遲指定毫秒後執行before(Animator anim)
:將現有動畫插入到傳入的動畫以前執行with(Animator anim)
:將現有動畫和傳入的動畫同時執行當咱們調用 AnimatorSet
的 play()
方法的時候就能獲取一個 AnimatorSet.Builder
實例,而後咱們就可使用構建者的方法進行鏈式調用了:
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(); 複製代碼
正如前文所述,屬性動畫也有本身的插值器,咱們能夠經過插值函數指定在某個時間段內屬性改變的速率。插值函數獲得的是一個比例,是沒有意義的。在 View 動畫的 AlphaAnimation
中,若是咱們指定了起止的透明度,那麼咱們能夠經過透明度的計算規則獲得某個時刻的透明度。可是對於屬性動畫,由於它能夠應用於任何屬性,這個屬性又多是任何類型的,那麼這個屬性將採用什麼樣的計算規則呢?這就須要咱們使用 TypeEvaluator
來指定一個計算規則。也就是說,TypeEvaluator
是屬性動畫的屬性的計算規則。
下面是 TypeEvaluator
的定義,這裏的三個參數的含義分別是,fraction
是當前的比例,能夠經過插值器計算獲得;startValue
和 endValue
分別是屬性變化的起止值。它的返回結果就是在某個時刻某個屬性的值。
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); } 複製代碼
屬性動畫中已經爲咱們提供了幾個預約義的 TypeEvaluator
,好比 FloatEvaluator
:
public class FloatEvaluator implements TypeEvaluator<Number> { public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } } 複製代碼
在屬性動畫的 PropertyValuesHolder
中會根據屬性的類型選擇預約義的 TypeEvaluator
。可是若是咱們的屬性的類型不在預約義的範圍以內就須要本身實現一個 TypeEvaluator
。下面咱們以日期類型爲例來實現一個 TypeEvaluator
。
當咱們使用 ValueAnimator
的 ofObject()
方法獲取 ValueAnimator
實例的時候,要求咱們傳入一個 TypeEvaluator
,因而咱們能夠像下面這樣定義:
private static class DateEvaluator implements TypeEvaluator<Date> { @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 以內輸出的從時間戳爲0,到當前時刻的全部的日期變化。
就像 View 動畫同樣,咱們能夠爲屬性動畫指定一個插值器。插值器的做用是用來設置指定時間段內數值的變化的速率。在屬性動畫中,插值器是 TimeInterpolator
,一樣也有幾個默認的實現:
AccelerateDecelerateInterolator
:先加速後減速。AccelerateInterpolator
:加速。DecelerateInterpolator
:減速。AnticipateInterpolator
:先向相反方向改變一段再加速播放。AnticipateOvershootInterpolator
:先向相反方向改變,再加速播放,會超出目標值而後緩慢移動至目標值,相似於彈簧回彈。BounceInterpolator
:快到目標值時值會跳躍。CycleIinterpolator
:動畫循環必定次數,值的改變爲一正弦函數:Math.sin(2 * mCycles * Math.PI * input)。LinearInterpolator
:線性均勻改變。OvershottInterpolator
:最後超出目標值而後緩慢改變到目標值。咱們能夠像下面這樣定義一個屬性動畫,
<set android:ordering=["together" | "sequentially"]> <objectAnimator 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"]/> <animator 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"]/> <set> ... </set> </set> 複製代碼
這裏的android:ordering
用於控制子動畫啓動方式是前後有序的仍是同時進行,兩個可選參數: sequentially
表示動畫按照前後順序;together
(默認)表示動畫同時啓動。
這裏的 <objectAnimator>
標籤的含義以下:
這樣在 XML 中定義了屬性動畫以後,咱們能夠在代碼中經過工具類獲取到動畫實例並使用:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animtor.property_animator); set.setTarget(myObject); set.start(); 複製代碼
View
動畫並無真正改變 View
的屬性,即 View
動畫執行以後並未改變 View
的真實佈局屬性值。譬如咱們在佈局中有一個 Button
在屏幕上方,咱們設置了平移動畫移動到屏幕下方而後保持動畫最後執行狀態呆在屏幕下方,這時若是點擊屏幕下方動畫執行以後的 Button
是沒有任何反應的,而點擊原來屏幕上方沒有 Button
的地方卻響應的是點擊 Button
的事件。px
在不一樣設備上面的兼容問題,使用動畫的時候儘可能使用 dp
做爲單位。你能夠在Github獲取以上程序的源代碼: Android-references。
想學習更多Android知識,請加入Android技術開發企鵝交流 7520 16839
進羣與大牛們一塊兒討論,還可獲取Android高級架構資料、源碼、筆記、視頻
包括 高級UI、Gradle、RxJava、小程序、Hybrid、移動架構、React Native、性能優化等全面的Android高級實踐技術講解性能優化架構思惟導圖,和BATJ面試題及答案!
羣裏免費分享給有須要的朋友,但願可以幫助一些在這個行業發展迷茫的,或者想系統深刻提高以及困於瓶頸的朋友,在網上博客論壇等地方少花些時間找資料,把有限的時間,真正花在學習上,因此我在這免費分享一些架構資料及給你們。但願在這些資料中都有你須要的內容。