這段時(shí)間正好要做些動(dòng)畫皿哨,于是把屬性動(dòng)畫重新學(xué)習(xí)了一遍,做些總結(jié)
1. 前言
Android動(dòng)畫分為Frame Animation
,Tweened Animation
笙蒙,Property Animation
既然已經(jīng)有了前兩種動(dòng)畫罕扎,為什么還要Property Animation
聚唐,核心點(diǎn)就是Property Animation
是改變對(duì)象的屬性,不僅僅是對(duì)view本身做操作
2. 關(guān)鍵類的使用
-
ObjectAnimator
動(dòng)畫的執(zhí)行類 -
ValueAnimator
動(dòng)畫的執(zhí)行類 -
AnimatorSet
控制一組動(dòng)畫的執(zhí)行 -
AnimatorInflater
加載屬性動(dòng)畫的xml文件 -
TypeEvaluator
類型估值腔召,主要用于設(shè)置動(dòng)畫操作屬性的值杆查。 -
TimeInterpolator
時(shí)間插值,定義動(dòng)畫變化率 -
LayoutTransition
布局動(dòng)畫臀蛛,為布局的容器設(shè)置動(dòng)畫 -
ViewPropertyAnimator
為View的動(dòng)畫操作提供一種更加便捷的用法
2.1 ObjectAnimator的使用
ObjectAnimator.ofFloat(view, "translationY", 0f, 500f)
.setDuration(500)
.start();
view在0.5秒向下滑動(dòng)500px的效果
2.2 ValueAnimator的使用
ValueAnimator.ofFloat(0f, 1f)
.setDuration(500)
.start();
屬性0.5秒的從0變成1
執(zhí)行了好像什么都沒發(fā)生啊亲桦,那我們添加個(gè)監(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();
日志如圖
確實(shí)在0.5秒內(nèi)打印了(這邊先提一下崖蜜,打印的輸出不是線性的,參見
TimeInterpolator
時(shí)間插值)于是實(shí)現(xiàn)view在0.5秒向下滑動(dòng)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先從屏幕外移動(dòng)進(jìn)屏幕客峭,然后開始旋轉(zhuǎn)360度豫领,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作
其實(shí)還有更簡單的方式,實(shí)現(xiàn)一個(gè)動(dòng)畫更改多個(gè)效果:使用propertyValuesHolder舔琅,幾個(gè)動(dòng)畫同時(shí)執(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中的屬性動(dòng)畫
在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的一個(gè)0.5秒淡出效果
2.5 TypeEvaluator的使用
ValueAnimator.ofFloat()方法就是實(shí)現(xiàn)了初始值與結(jié)束值之間的平滑過度,那么這個(gè)平滑過度是怎么做到的呢备蚓?其實(shí)就是系統(tǒng)內(nèi)置了一個(gè)FloatEvaluator鼠锈,它通過計(jì)算告知?jiǎng)赢嬒到y(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
中還有一個(gè)ofObject()
方法,是用于對(duì)任意對(duì)象進(jìn)行動(dòng)畫操作的
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();
通過對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作星著,從而實(shí)現(xiàn)整個(gè)自定義View的動(dòng)畫效果购笆。
2.6 TimeInterpolator的使用
ValueAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
anim.setDuration(1000);
anim.setInterpolator(new LinearInterpolator());
anim.start();
設(shè)置了一個(gè)勻速運(yùn)動(dòng)
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
當(dāng)一個(gè)View在ViewGroup中出現(xiàn)時(shí),對(duì)此View設(shè)置的動(dòng)畫
LayoutTransition.CHANGE_APPEARING
當(dāng)一個(gè)View在ViewGroup中出現(xiàn)時(shí)虚循,對(duì)此View對(duì)其他View位置造成影響同欠,對(duì)其他View設(shè)置的動(dòng)畫
LayoutTransition.DISAPPEARING
當(dāng)一個(gè)View在ViewGroup中消失時(shí),對(duì)此View設(shè)置的動(dòng)畫
LayoutTransition.CHANGE_DISAPPEARING
當(dāng)一個(gè)View在ViewGroup中消失時(shí)横缔,對(duì)此View對(duì)其他View位置造成影響铺遂,對(duì)其他View設(shè)置的動(dòng)畫
LayoutTransition.CHANGE
不是由于View出現(xiàn)或消失造成對(duì)其他View位置造成影響,然后對(duì)其他View設(shè)置的動(dòng)畫茎刚。
注意動(dòng)畫到底設(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)聽到動(dòng)畫的各種事件膛锭,如果覺得不想用到這么多粮坞,可以用AnimatorListenerAdapter
,這個(gè)抽象類有對(duì)AnimatorListener
的空實(shí)現(xiàn)初狰,這樣就可以單獨(dú)重寫某個(gè)事件了
anim.addListener(new AnimatorListenerAdapter() {
});
3. 關(guān)鍵類的詳解
3.1 ObjectAnimator
上面用了ofFloat
還有ofInt
莫杈、ofFloat
、ofObject
奢入,這幾個(gè)方法都是設(shè)置動(dòng)畫作用的元素筝闹、作用的屬性,動(dòng)畫開始腥光、結(jié)束关顷、以及中間的任意個(gè)屬性值。
當(dāng)設(shè)置1個(gè)值武福,則為從當(dāng)前屬性開始改變
當(dāng)設(shè)置2個(gè)值议双,則一個(gè)為開始、一個(gè)為結(jié)束
當(dāng)設(shè)置多個(gè)值艘儒,則依次改變
來看看ofFloat
的具體實(shí)現(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);
于是存入了一個(gè)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);
}
這里有個(gè)KeyframeSet觉增,是Keyframe的集合,而Keyframe叫做關(guān)鍵幀翻斟,為一個(gè)動(dòng)畫保存time/value(時(shí)間與值)對(duì)逾礁。再看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被拆分成了許多時(shí)間片fraction
上面都是存儲(chǔ)設(shè)置,這邊還有個(gè)疑問访惜,那就是我設(shè)置的propertyName
是如何利用的呢嘹履,我們往下看
因?yàn)?code>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);
}
我們?cè)诳椿?code>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
我們?cè)偃?code>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)部的工作機(jī)制并不是直接對(duì)我們傳入的屬性名進(jìn)行操作的债热,而是會(huì)去尋找這個(gè)屬性名對(duì)應(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()方法中接收一個(gè)input參數(shù)砾嫉,這個(gè)參數(shù)的值會(huì)隨著動(dòng)畫的運(yùn)行而不斷變化,不過它的變化是非常有規(guī)律的窒篱,就是根據(jù)設(shè)定的動(dòng)畫時(shí)長勻速增加焕刮,變化范圍是0到1。也就是說當(dāng)動(dòng)畫一開始的時(shí)候input的值是0墙杯,到動(dòng)畫結(jié)束的時(shí)候input的值是1配并,而中間的值則是隨著動(dòng)畫運(yùn)行的時(shí)長在0到1之間變化的。
而input的值決定了fraction的值高镐。input的值是由系統(tǒng)經(jīng)過計(jì)算后傳入到getInterpolation()方法中的溉旋,然后我們可以自己實(shí)現(xiàn)getInterpolation()方法中的算法,根據(jù)input的值來計(jì)算出一個(gè)返回值嫉髓,而這個(gè)返回值就是fraction了观腊。
參考:
Android 屬性動(dòng)畫(Property Animation) 完全解析 (上)
Android 屬性動(dòng)畫(Property Animation) 完全解析 (下)
Android 屬性動(dòng)畫 源碼解析 深入了解其內(nèi)部實(shí)現(xiàn)
Android屬性動(dòng)畫完全解析(上)
Android屬性動(dòng)畫完全解析(中)
Android屬性動(dòng)畫完全解析(下)