Android屬性動畫學習筆記

這段時間正好要做些動畫,于是把屬性動畫重新學習了一遍,做些總結(jié)

1. 前言

Android動畫分為Frame Animation,Tweened Animation ,Property Animation
既然已經(jīng)有了前兩種動畫,為什么還要Property Animation,核心點就是Property Animation是改變對象的屬性,不僅僅是對view本身做操作

2. 關(guān)鍵類的使用

  1. ObjectAnimator 動畫的執(zhí)行類
  2. ValueAnimator 動畫的執(zhí)行類
  3. AnimatorSet 控制一組動畫的執(zhí)行
  4. AnimatorInflater 加載屬性動畫的xml文件
  5. TypeEvaluator 類型估值,主要用于設(shè)置動畫操作屬性的值。
  6. TimeInterpolator 時間插值,定義動畫變化率
  7. LayoutTransition 布局動畫,為布局的容器設(shè)置動畫
  8. ViewPropertyAnimator 為View的動畫操作提供一種更加便捷的用法
2.1 ObjectAnimator的使用
ObjectAnimator.ofFloat(view, "translationY", 0f, 500f)
    .setDuration(500)
    .start();

view在0.5秒向下滑動500px的效果

2.2 ValueAnimator的使用
ValueAnimator.ofFloat(0f, 1f)
    .setDuration(500)
    .start();

屬性0.5秒的從0變成1
執(zhí)行了好像什么都沒發(fā)生啊,那我們添加個監(jiān)聽器看看

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(500);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        float currentValue = (float) valueAnimator.getAnimatedValue();
        Log.d(TAG, "current value is " + currentValue);
    }
});
anim.start();

日志如圖


確實在0.5秒內(nèi)打印了(這邊先提一下,打印的輸出不是線性的,參見TimeInterpolator 時間插值)
于是實現(xiàn)view在0.5秒向下滑動500px的效果

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setTarget(view);
anim.setDuration(500);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        float currentValue = (float) valueAnimator.getAnimatedValue();
        view.setTranslationY(currentValue * 500);
    }
});
anim.start();
2.3 AnimatorSet的使用
ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);animSet.setDuration(5000);
animSet.start();

view先從屏幕外移動進屏幕,然后開始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時進行淡入淡出操作

其實還有更簡單的方式,實現(xiàn)一個動畫更改多個效果:使用propertyValuesHolder,幾個動畫同時執(zhí)行

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha",1f,0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(1000).start();
2.4 AnimatorInflater的使用

加載xml中的屬性動畫
在res下建立animator文件夾,然后建立res/animator/alpha.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:propertyName="alpha"
    android:valueFrom="1.0"
    android:valueTo="0.0"
    android:valueType="floatType" >
</objectAnimator>
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.alpha);
anim.setTarget(view);
anim.start();

view的一個0.5秒淡出效果

2.5 TypeEvaluator的使用

ValueAnimator.ofFloat()方法就是實現(xiàn)了初始值與結(jié)束值之間的平滑過度,那么這個平滑過度是怎么做到的呢?其實就是系統(tǒng)內(nèi)置了一個FloatEvaluator,它通過計算告知動畫系統(tǒng)如何從初始值過度到結(jié)束值

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);  
    }  
}  

ValueAnimator中還有一個ofObject()方法,是用于對任意對象進行動畫操作的

public class PointEvaluator implements TypeEvaluator{  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
}  

重寫了evaluate()方法

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();  

通過對Point對象進行動畫操作,從而實現(xiàn)整個自定義View的動畫效果。

2.6 TimeInterpolator的使用
ValueAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
anim.setDuration(1000);
anim.setInterpolator(new LinearInterpolator());
anim.start();

設(shè)置了一個勻速運動

2.7 LayoutTransition的使用
LayoutTransition transition = new LayoutTransition();  
    transition.setAnimator(LayoutTransition.CHANGE_APPEARING,  
            transition.getAnimator(LayoutTransition.CHANGE_APPEARING));  
    transition.setAnimator(LayoutTransition.APPEARING,  
            null);  
    transition.setAnimator(LayoutTransition.DISAPPEARING,  
            null);  
    transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,  
            null);  
    mGridLayout.setLayoutTransition(transition);  

過渡的類型一共有四種:
LayoutTransition.APPEARING 當一個View在ViewGroup中出現(xiàn)時,對此View設(shè)置的動畫
LayoutTransition.CHANGE_APPEARING 當一個View在ViewGroup中出現(xiàn)時,對此View對其他View位置造成影響,對其他View設(shè)置的動畫
LayoutTransition.DISAPPEARING 當一個View在ViewGroup中消失時,對此View設(shè)置的動畫
LayoutTransition.CHANGE_DISAPPEARING 當一個View在ViewGroup中消失時,對此View對其他View位置造成影響,對其他View設(shè)置的動畫
LayoutTransition.CHANGE 不是由于View出現(xiàn)或消失造成對其他View位置造成影響,然后對其他View設(shè)置的動畫。
注意動畫到底設(shè)置在誰身上,此View還是其他View。

2.8 ViewPropertyAnimator
view.animate().x(500).y(500).setDuration(5000)  
        .setInterpolator(new BounceInterpolator());  
2.9 Animator的監(jiān)聽器
anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
}); 

可以監(jiān)聽到動畫的各種事件,如果覺得不想用到這么多,可以用AnimatorListenerAdapter,這個抽象類有對AnimatorListener的空實現(xiàn),這樣就可以單獨重寫某個事件了

anim.addListener(new AnimatorListenerAdapter() {  
});  

3. 關(guān)鍵類的詳解

3.1 ObjectAnimator

上面用了ofFloat
還有ofIntofFloatofObject,這幾個方法都是設(shè)置動畫作用的元素、作用的屬性,動畫開始、結(jié)束、以及中間的任意個屬性值。
當設(shè)置1個值,則為從當前屬性開始改變
當設(shè)置2個值,則一個為開始、一個為結(jié)束
當設(shè)置多個值,則依次改變
來看看ofFloat的具體實現(xiàn),主要看propertyName參數(shù)

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setFloatValues(values);
    return anim;
}
  1. 先看構(gòu)造方法
private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    setPropertyName(propertyName);}
public void setPropertyName(@NonNull String propertyName) {
    // mValues could be null if this is being constructed piecemeal. Just record the
    // propertyName to be used later when setValues() is called if so.
    if (mValues != null) {
        PropertyValuesHolder valuesHolder = mValues[0];
        String oldName = valuesHolder.getPropertyName();
        valuesHolder.setPropertyName(propertyName);
        mValuesMap.remove(oldName);
        mValuesMap.put(propertyName, valuesHolder);
    }
    mPropertyName = propertyName;
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

mValuesMap.put(propertyName, valuesHolder);于是存入了一個map,key是propertyName,value是存有propertyName的PropertyValuesHolder

  1. 再看anim.setFloatValues(values);
@Overridepublic void setFloatValues(float... values) {
    if (mValues == null || mValues.length == 0) {
        // No values yet - this animator is being constructed piecemeal. Init the values with
        // whatever the current propertyName is
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
    return new FloatPropertyValuesHolder(propertyName, values);}
public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}
@Overridepublic void setFloatValues(float... values) {
    super.setFloatValues(values);
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

這里有個KeyframeSet,是Keyframe的集合,而Keyframe叫做關(guān)鍵幀,為一個動畫保存time/value(時間與值)對。再看KeyframeSet.ofFloat(values)

public static KeyframeSet ofFloat(float... values) {
    boolean badValue = false;
    int numKeyframes = values.length;
    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
    if (numKeyframes == 1) {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
        keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
        if (Float.isNaN(values[0])) {
            badValue = true;
        }
    } else {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
            if (Float.isNaN(values[i])) {
                badValue = true;
            }
        }
    }
    if (badValue) {
        Log.w("Animator", "Bad value (NaN) in float animator");
    }
    return new FloatKeyframeSet(keyframes);
}
public static Keyframe ofFloat(float fraction) {
    return new FloatKeyframe(fraction);
}
public static Keyframe ofFloat(float fraction, float value) {
    return new FloatKeyframe(fraction, value);
}

value被拆分成了許多時間片fraction
上面都是存儲設(shè)置,這邊還有個疑問,那就是我設(shè)置的propertyName是如何利用的呢,我們往下看

因為ObjectAnimator extends ValueAnimator,我們來看ValueAnimatorstart()函數(shù)

@Overridepublic void start() {
    start(false);
}
private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mPlayingBackwards = playBackwards;
    if (playBackwards && mSeekFraction != -1) {
        if (mSeekFraction == 0 && mCurrentIteration == 0) {
            // special case: reversing from seek-to-0 should act as if not seeked at all            mSeekFraction = 0;
        } else if (mRepeatCount == INFINITE) {
            mSeekFraction = 1 - (mSeekFraction % 1);
        } else {
            mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
        }
        mCurrentIteration = (int) mSeekFraction;
        mSeekFraction = mSeekFraction % 1;
    }
    if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
            (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
        // if we were seeked to some other iteration in a reversing animator,
        // figure out the correct direction to start playing based on the iteration
        if (playBackwards) {
            mPlayingBackwards = (mCurrentIteration % 2) == 0;
        } else {
            mPlayingBackwards = (mCurrentIteration % 2) != 0;
        }
    }
    int prevPlayingState = mPlayingState;
    mPlayingState = STOPPED;
    mStarted = true;
    mStartedDelay = false;
    mPaused = false;
    updateScaledDuration();
 // in case the scale factor has changed since creation time
    AnimationHandler animationHandler = getOrCreateAnimationHandler();
    animationHandler.mPendingAnimations.add(this);
    if (mStartDelay == 0) {
        // This sets the initial value of the animation, prior to actually starting it running
        if (prevPlayingState != SEEKED) {
            setCurrentPlayTime(0);
        }
        mPlayingState = STOPPED;
        mRunning = true;
        notifyStartListeners();
    }
    animationHandler.start();}

setCurrentPlayTime(0)

public void setCurrentPlayTime(long playTime) { 
   float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1; 
   setCurrentFraction(fraction);
}
public void setCurrentFraction(float fraction) {
    ...
    animateValue(fraction);
}

我們在看回ObjectAnimator

@Overridevoid animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        // We lost the target reference, cancel and clean up.
        cancel();
        return;
    }
    super.animateValue(fraction);
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);
    }
}

PropertyValuesHolder[] mValues我們再去PropertyValuesHolder中看

Method mSetter = null;
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());
        }
    }
}

因此ObjectAnimator內(nèi)部的工作機制并不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應(yīng)的get和set方法

3.2 TimeInterpolator
public interface TimeInterpolator {
    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

getInterpolation()方法中接收一個input參數(shù),這個參數(shù)的值會隨著動畫的運行而不斷變化,不過它的變化是非常有規(guī)律的,就是根據(jù)設(shè)定的動畫時長勻速增加,變化范圍是0到1。也就是說當動畫一開始的時候input的值是0,到動畫結(jié)束的時候input的值是1,而中間的值則是隨著動畫運行的時長在0到1之間變化的。
而input的值決定了fraction的值。input的值是由系統(tǒng)經(jīng)過計算后傳入到getInterpolation()方法中的,然后我們可以自己實現(xiàn)getInterpolation()方法中的算法,根據(jù)input的值來計算出一個返回值,而這個返回值就是fraction了。

參考:
Android 屬性動畫(Property Animation) 完全解析 (上)
Android 屬性動畫(Property Animation) 完全解析 (下)
Android 屬性動畫 源碼解析 深入了解其內(nèi)部實現(xiàn)
Android屬性動畫完全解析(上)
Android屬性動畫完全解析(中)
Android屬性動畫完全解析(下)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容