Android動畫(二)-屬性動畫
概述
上一篇主要介紹了ViewAnim和幀動畫,篇幅有點長,另起一篇。上篇介紹的兩種動畫開發中用到的不多,主要還是本篇的屬性動畫使用比較廣。
1 補間動畫
1.1 Property Anim
開發中Property Anim使用比View Anim要更為廣泛,主要還是出於剛剛提到過的View Anim執行之後View的位置沒有變化。
有的時候我們確實是需要改變View位置的。
1.1.1 Object Anim
使用之前先介紹一些屬性的含義
(1) translationX 和 translationY:這兩個屬性控制著 View 的螢幕位置座標變化量,以 layout 容器的左上角為座標原點;
(2) rotation、rotationX 和 rotationY:這三個屬性控制著 2D 旋轉角度(rotation屬性)和圍繞某樞軸點的 3D 旋轉角度;
(3) scaleX、scaleY:這兩個屬性控制著 View 圍繞某樞軸點的 2D 縮放比例;
(4) pivotX 和 pivotY: 這兩個屬性控制著樞軸點的位置,前述的旋轉和縮放都是以此點為中心展開的,預設的樞軸點是 View 物件的中心點;
(5) x 和 y:這是指 View 在容器內的最終位置,等於 View 左上角相對於容器的座標加上 translationX 和 translationY 後的值;
(6)alpha:表示 View 的 alpha 透明度。預設值為 1 (不透明),為 0 則表示完全透明(看不見);
補間動畫能實現的他都可以實現,舉個栗子!
例1:從當前位置移動到Y軸300的位置
private void startObjAnim() {
// 第一個引數是執行動畫的View,第二個引數是該View需要改變的屬性,第三個引數是執行開始Y座標,以螢幕座標系為參照,第四個引數是移動到的位置
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mContentIv, "Y", mContentIv.getY(), 300);
objectAnimatorY.setDuration(2000 );//動畫執行時間
objectAnimatorY.setRepeatCount(2);//重複次數
objectAnimatorY.setRepeatMode(ValueAnimator.REVERSE);//重複模式
objectAnimatorY.setInterpolator(new BounceInterpolator());//插值器
objectAnimatorY.start();
}
這個例子不難看懂,但是如果想同時移動X,Y,並且要閃爍旋轉呢?
例2:從當前位置移動到(100,300),移動中閃爍旋轉
這裡就不能再用AnimationSet了,因為ObjectAnimator是Animator的子類,我們使用AnimatorSet
private void startObjAnim1() {
AnimatorSet animatorSet = new AnimatorSet();
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mContentIv, "Y", mContentIv.getY(), 300);
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(mContentIv, "X", mContentIv.getX(), 100);
ObjectAnimator objectAnimatorRx = ObjectAnimator.ofFloat(mContentIv, "rotationX", 0f, 180f);
ObjectAnimator objectAnimatorSx = ObjectAnimator.ofFloat(mContentIv, "scaleX", 1f, 0.5f);
ObjectAnimator objectAnimatorA = ObjectAnimator.ofFloat(mContentIv, "alpha", 1f, 0.5f);
animatorSet.play(objectAnimatorX).with(objectAnimatorY).with(objectAnimatorA).with(objectAnimatorSx).with(objectAnimatorRx);
animatorSet.setDuration(3000);
animatorSet.start();
}
不難看懂,Animator一路構造,也可以指定在某個動畫之前或之後呼叫,將with()換為befer()|after()。
之前在用ViewAnim做旋轉的時候提了一個面試題,旋轉之後保持現在的位置,那麼用Property Anim就可以做了。
例3:播放按鈕
//開始
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void startObjAnim2() {
if (objectAnimatorR == null) {
objectAnimatorR = ObjectAnimator.ofFloat(mContentIv, "rotation", 0f, 360f);
objectAnimatorR.setDuration(3000);
objectAnimatorR.setRepeatCount(3);
objectAnimatorR.start();
}else{
objectAnimatorR.resume();
}
}
//暫停
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void switchAnimStop(String selectedItem) {
if (objectAnimatorR != null && objectAnimatorR.isStarted()) {
objectAnimatorR.pause();
}
}
主要就是Animator提供了一個pause和resume方法,簡單看下內部做了什麼事情。
/**
* 暫停執行中的動畫,如果動畫已經結束或還沒開始,則該方法無效。
* 可以通過呼叫resume()方法來繼續執行動畫
*/
public void pause() {
if (isStarted() && !mPaused) {
mPaused = true;
if (mPauseListeners != null) {
ArrayList<AnimatorPauseListener> tmpListeners =
(ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationPause(this);
}
}
}
}
在執行pause的時候將mPaused置為true,動畫暫停了,可以思考一下,動畫執行中也是逐幀執行,應該是一個迴圈,並且還判斷了當前的pause狀態。那麼我們接著看這個猜想是否正確,看下動畫是如何執行的。
我們呼叫start方法,動畫開始執行。
private void start(boolean playBackwards) {
...
mStarted = true;
mPaused = false;
mRunning = false;
...
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
這裡去掉了一些程式碼和註釋,我們想一下動畫執行也是很多工幀,應該也是一個執行緒去處理這個事情。
點進startAnimation和setCurrentFraction方法去看,都是一些設定isRunning,mSeekFraction等一些值的操作,沒有看到Thread,Runnable這些的影子。於是我們看下AnimationHandler.addAnimationFrameCallback
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
mAnimationCallbacks這個明顯是個集合放了很多監聽器,我們在第一次呼叫的時候走的應該是mAnimationCallbacks.size() == 0這個邏輯,那麼下面那個Provider是個什麼鬼??
點進去看他new 了一個MyFrameCallbackProvider,這裡面有一個類Choreographer,在點下去看!
介紹是 * Coordinates the timing of animations, input and drawing.關聯了動畫時間,輸出和繪製!好像是我們在找的東西!再往下看他的介紹,確認了他就是讓動畫執行的類。
啊哈!看到了ThreadLocal,終於被我們找到了!但是並不是我們需要的。在看,postFrameCallback,一路點下去有個doCallbacks的方法,看一下。
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
... //這段去掉,我們只看CALLBACK_ANIMATION的部分
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
} ...//省略了finally處理,
}
看到呼叫了一個run方法,點進去看下,看到他呼叫了傳入回撥的doFrame方法回傳了frameTimeNanos,如果不是FRAME_CALLBACK_TOKEN則呼叫runnable的run
計算完grameTimeNanos後回撥給了AnimatorHandler執行doAnimationFrame,並繼續下一幀的計算
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
看下doAnimationFrame回撥到了ValueAnimtor裡面的方法
public final void doAnimationFrame(long frameTime) {
...
if (mPaused) {
mPauseTime = frameTime;
handler.removeCallback(this);
return;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
handler.addOneShotCommitCallback(this);
}
...
}
終於被我們找到了如果是暫停狀態則移除回撥監聽,不會在繪製,當我們在呼叫resume的時候有添加了callback繼續繪製,直到動畫完成。
不知不覺看了有點多,小結一下
- ObjectAnim是通過執行緒計算每一幀繪製的
- 在呼叫pause時候任務還在進行,但是不會走回調顯示的方法
- 重新resume之後接著上次的位置繼續繪製直到動畫結束
因此我們的需求可以滿足,也可以看出來其實pause之後動畫任務還是在消耗資源
1.1.2 ValueAnimtor實現動畫
經過上面的分析也不難看出ValueAnimtor是ObjAnim的父類,他們都是Animtor的子類。當然使用ObjectAnimtor要比ValueAnimtor方便,這裡也介紹下ValueAnimtor是怎麼用的吧~
例1:將View縮放到自身的一半,用ValueAnimtor實現
private void startValueAnim1() {
//1. 建立控制代碼
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.5f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 0.5f);
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleX, scaleY);
//2. 設定屬性變化監聽
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//3. 獲取變化值並設定給View
float animatorValueScaleX = (float) animation.getAnimatedValue("scaleX");
float animatorValueScaleY = (float) animation.getAnimatedValue("scaleY");
mContentIv.setScaleX(animatorValueScaleX);
mContentIv.setScaleY(animatorValueScaleY);
}
});
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(2);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.start();
}
1.2 Layout Anim
除了上述的幾種動畫外,還有一種動畫,在開發中也遇到了,下面也介紹一下佈局動畫。
1.2.1 LayoutAnimation
LayoutAnimation 是API Level 1 就已經有的,LayoutAnimation是對於ViewGroup控制元件所有的child view的操作,也就是說它是用來控制ViewGroup中所有的child view 顯示的動畫。
XML實現方式
1.在res/anim包下面申明動畫效果 anim_top_to_down.xml
<?xml version="1.0" encoding="utf-8"?>
<!--向下移動到介面一半並透明-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:repeatMode="reverse"
android:duration="3000">
<translate
android:fromYDelta="0"
android:toYDelta="50%"/>
<alpha
android:fromAlpha="1"
android:toAlpha="0.5"/>
</set>
2.在res/anim包下宣告anim_cust_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/anim_top_to_down"
android:animationOrder="reverse"
android:delay="30%"/>
3.在佈局檔案中新增
android:layoutAnimation="@anim/anim_cust_layout"
程式碼實現
//通過載入XML動畫設定檔案來建立一個Animation物件;
Animation animation=AnimationUtils.loadAnimation(this, R.anim.slide_right); //得到一個LayoutAnimationController物件;
LayoutAnimationController controller = new LayoutAnimationController(animation); //設定控制元件顯示的順序;
controller.setOrder(LayoutAnimationController.ORDER_REVERSE); //設定控制元件顯示間隔時間;
controller.setDelay(0.3); //為ListView設定LayoutAnimationController屬性;
listView.setLayoutAnimation(controller);
listView.startLayoutAnimation();
1.2.2 LayoutTransition
LayoutTransition 是API Level 11 才出現的。LayoutTransition的動畫效果,只有當ViewGroup中有View新增、刪除、隱藏、顯示的時候才會體現出來。
- 在xml中
android:animateLayoutChanges="true"
- 為ViewGroup新增動畫
LayoutTransition mTransitioner = new LayoutTransition();
mViewGroup.setLayoutTransition(mTransitioner);
其實也是ObjectAnimtor實現的。
學習的時候在網上看到一個例子,也記錄一下。
原文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0619/3090.html
文中是使用佈局動畫改編每個佈局的播放順序實現的。我們換一種方式
使用了ObjAnim的方式,自己感覺這樣確實很蠢,使用了動畫延遲
public void scaleView(List<View> vs) {
int l = 0;
AnimatorSet set = new AnimatorSet();
for (int i = 0; i < vs.size(); i++) {
View v = vs.get(i);
if (i == vs.size() / 2)
l = 100;
l += 100;
AnimatorSet itemSet = new AnimatorSet();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(v, "scaleX", 1f, 0f);
scaleX.setRepeatMode(ValueAnimator.RESTART);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(v, "scaleY", 1f, 0f);
scaleY.setRepeatMode(ValueAnimator.RESTART);
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, "alpha", 1f, 0f);
alpha.setRepeatMode(ValueAnimator.RESTART);
itemSet.play(scaleX).with(scaleY).with(alpha);
itemSet.setDuration(250);
itemSet.setStartDelay(l);
set.play(itemSet);
}
set.start();
}
1.3 插值器(Interpolator)
計算執行速度的類。
1.3.1 常見插值器
- AccelerateDecelerateInterpolator 在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速
- AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
- AnticipateInterpolator 開始的時候向後甩一點然後向前
- AnticipateOvershootInterpolator 開始的時候向後甩一點然後向前超過設定值一點然後返回
- BounceInterpolator 動畫結束的時候彈起,類似皮球落地
- CycleInterpolator 動畫迴圈播放特定的次數回到原點,速率改變沿著正弦曲線
- DecelerateInterpolator 在動畫開始的地方快然後慢
- LinearInterpolator 以常量速率改變
- OvershootInterpolator 向前超過設定值一點然後返回
此處參考:http://blog.csdn.net/daydayplayphone/article/details/52503665
有圖可以自己去看更直觀,其實很簡單,常用的記住就好,英文意思很明確了
下面我們看下如何自己定義一個插值器!
1.3.2自定義插值器
自定義插值器需要實現 Interpolator / TimeInterpolator介面 & 複寫getInterpolation()
- 補間動畫 實現 Interpolator介面;屬性動畫實現TimeInterpolator介面
- TimeInterpolator介面是屬性動畫中新增的,用於相容Interpolator介面,這使得所有過去的Interpolator實現類都可以直接在屬性動畫使用
/**
* 插值分數
* @param input 值在0~1之間,表明當前點在動畫中的位置,0是起始點,1是結束點
* @return 插值分數< 0低於目標,>1則超過目標
*/
float getInterpolation(float input);
1.4 估值器(TypeEvaluator)
作用:設定 屬性值 從初始值過渡到結束值 的變化具體數值
- 插值器(Interpolator)決定 值 的變化規律(勻速、加速blabla),即決定的是變化趨勢;而接下來的具體變化數值則交給估值器
- 協助插值器 實現非線性運動的動畫效果
// 在第4個引數中傳入對應估值器類的物件
ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "height", new Evaluator(),1,3);
系統內建的估值器有3個:
- IntEvaluator:以整型的形式從初始值 - 結束值 進行過渡
- FloatEvaluator:以浮點型的形式從初始值 - 結束值 進行過渡
- ArgbEvaluator:以Argb型別的形式從初始值 - 結束值 進行過渡
*注 那麼插值器的input值 和 估值器fraction有什麼關係呢?
答:input的值決定了fraction的值:input值經過計算後傳入到插值器的getInterpolation(),然後通過實現getInterpolation()中的邏輯演算法,根據input值來計算出一個返回值,而這個返回值就是fraction了