這段時間正好要做些動畫,于是把屬性動畫重新學習了一遍,做些總結(jié)
1. 前言
Android動畫分為Frame Animation
,Tweened Animation
,Property Animation
既然已經(jīng)有了前兩種動畫,為什么還要Property Animation
,核心點就是Property Animation
是改變對象的屬性,不僅僅是對view本身做操作
2. 關(guān)鍵類的使用
-
ObjectAnimator
動畫的執(zhí)行類 -
ValueAnimator
動畫的執(zhí)行類 -
AnimatorSet
控制一組動畫的執(zhí)行 -
AnimatorInflater
加載屬性動畫的xml文件 -
TypeEvaluator
類型估值,主要用于設(shè)置動畫操作屬性的值。 -
TimeInterpolator
時間插值,定義動畫變化率 -
LayoutTransition
布局動畫,為布局的容器設(shè)置動畫 -
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
還有ofInt
、ofFloat
、ofObject
,這幾個方法都是設(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;
}
- 先看構(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
- 再看
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
,我們來看ValueAnimator
的start()
函數(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屬性動畫完全解析(下)