Android属性动画
一、简介
在Android3.0为了取代传统诞生的产物,相较于传统动画有着很灵活的优势。
**传统动画:**仅仅只能实现平移、缩放、旋转、淡入淡出这四种效果,并且对Button实现从左上角平移到右下角,点击右下角的Button时,并不能触发click事件,也就是说传统的补间动画并未对View的属性做到修改;以及传统动画仅仅只限于对View及其子类做动画。
**属性动画:**可扩展性较高,实现简单,不仅仅可以实现上述传统动画的四种效果,也可以实现ObjectAnimator对任意对象实现动画效果,并且真正意义上的对属性进行了改变。
二、属性动画的用法
2.1 ValueAnimator
ValueAnimator是属性动画的一个核心类,在第一节中说到了Animator是对value属性值进行操作的,初始值和结束值之间的过渡动画就是通过ValueAnimator.ofFloat(0f,1f)这句关键代码传入的,由于没有传入属性的具体值,因此需要通过addUpdateListener设置监听来观察value值的变化;参数设置完毕后,必须要调用animator.start()启动动画。
public void scale(View view) {
//创建ValueAnimator,并且实现value从0-1的变化
ValueAnimator animator = ValueAnimator.ofFloat(0f,1f);
//设置动画持续时间为1000
animator.setDuration(1000);
//监听动画value值的改变,当value变化时回调onAnimationUpdate
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.d("kkjj","animation-->"+animation.getAnimatedValue());
}
});
//启动动画
animator.start();
}
但属性动画是非常灵活的,我们也可以设置多个动画中间值,如下:
public void scale(View view) {
//创建ValueAnimator,并且实现value从0-1的变化
ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);
除了上述可以设置属性动画的API以外,还有其他很多可供开发者选择,如下所示:setRepeatCount和setRepeatMode是重复播放相关的
//设置动画重复次数
animator.setRepeatCount(5);
//设置动画重复播放的模式是从头开始RESTART,还是从倒播动画REVERSE,默认RESTART
animator.setRepeatMode(ValueAnimator.RESTART);
2.2 ObjectAnimator
第一节中说到属性动画可以对任意对象实现动画效果,而不仅仅只限于View。在属性动画中提供了另一个核心类ObjectAnimator,其继承于ValueAnimator。
相比于ValueAnimator,ObjectAnimator从名字上看似乎更加适合开发者,因为ValueAnimator只不过是对value值进行动画变化,但使用场景似乎有限,因此Google推出了其ObjectAnimator,它可以对任意对象的任意属性实现动画效果。但虽然其功能强大,由于其继承自ValueAnimator,底层还是基于ValueAnimator完成的,由于继承关系,ValueAnimator的一些API,在ObjectAnimator上也能够使用。
其用法和ValueAnimator差不多,这里不做过多说明。这里需要说明以下两个API的作用,其原理在第三节源码解析叙述。
TypeEvaluator:估值器,设置属性值从初始值过渡到结束值 的变化具体数值。
Interpolator:插值器,设置属性值从初始状态,到结束状态的变化规律,有匀速、加速、减速。
public void scale1(View view) {
Button button = (Button)view;
ObjectAnimator animator = (ObjectAnimator) ObjectAnimator.ofFloat(button,"alpha",0f,1f);
animator.setDuration(3000);
//设置重复次数
animator.setRepeatCount(5);
//设置重复模式,默认为RESTART
animator.setRepeatMode(ObjectAnimator.REVERSE);
//设置插值器
animator.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float input) {
return input * input;
}
});
//设置估值器
animator.setEvaluator(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
float t = 5 * fraction*1.0f;
PointF pointF = new PointF();
pointF.x = 150 * t;
pointF.y = 0.5f * 98.0f * t * t;
return pointF;
}
});
animator.start();
}
2.3 多属性动画
上面代码都是改变某一个属性的动画,例如有如下场景:将Button的x,y坐标从左上角移动到右下角,在这个过程中同时将Button的透明度从1改变到0。
public void scale3(View view) {
//通过PropertyValuesHolder分装多个属性值
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX",0f,320f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY",0f,480f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha",1f,0f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder((Button)view,holder1,holder2,holder3);
animator.setDuration(3000);
//设置重复次数
animator.setRepeatCount(5);
//设置重复模式,默认为RESTART
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.start();
}
2.4 组合动画
也可以实现组合动画,比如先移动Button的y坐标,再移动x坐标,最后实现透明度的变换。
AnimatorSet 中提供play方法,它会返回一个Animator.Builder的构建者,并且Animator.Builder提供了如下四个方法。
/**
* after(Animator anim) 将现有动画插入到传入的动画之后执行
* after(long delay) 将现有动画延迟指定毫秒后执行
* before(Animator anim) 将现有动画插入到传入的动画之前执行
* with(Animator anim) 将现有动画和传入的动画同时执行
* @param view
*/
public void scale4(View view) {
ObjectAnimator yAnimator = ObjectAnimator.ofFloat((Button)view,"scaleY",0f,480f);
ObjectAnimator xAnimator = ObjectAnimator.ofFloat((Button)view,"scaleX",0f,320f);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat((Button)view,"alpha",1f,0f);
AnimatorSet set = new AnimatorSet();
set.play(yAnimator).after(xAnimator).with(alphaAnimator);
set.setDuration(3000);
set.start();
}
三、源码解析
3.1 ValueAnimator源码
(1)先从ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);开始,可以看出在ofFloat中创建了对象,但ValueAnimator是空的,没什么可看的
疑问:ofFloat传入的参数代表什么?
public static ValueAnimator ofFloat(float... values) {
//创建了ValueAnimator对象,并且将我们传入的关键帧传入setFloatValues
ValueAnimator anim = new ValueAnimator();
anim.setFloatValues(values);
return anim;
}
public ValueAnimator() {
}
public void setFloatValues(float... values) {
//当开发者未传入values时,直接返回
if (values == null || values.length == 0) {
return;
}
//第一次进入时,mValue为空,进入此方法
if (mValues == null || mValues.length == 0) {
//PropertyValuesHolder.ofFloat初始化了KeyFrameSet,并且将关键帧存储起来了
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
此处propertyName传入的“”,返回的是PropertyValuesHolder的子类FloatPropertyValuesHolder
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
return new FloatPropertyValuesHolder(propertyName, values);
}
属性名最终被保存在了PropertyValuesHolder的mPropertyName属性中
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
继续往下看FloatPropertyValuesHolder的setFloatValues,在这里将value值交给父类PropertyValuesHolder的setFloatValues处理
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
PropertyValuesHolder初始化了setFloatValues和KeyframeSet,这里的KeyFrameSet是保存了所有关键帧的集合
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
这里主要是根据开发者传入的values值,为每一个value值创建一个关键帧,然后将这些关键帧封装到了KeyframeSet中。
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
//获取我们传入的Values数组长度
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 {
//否则根据传入的所有Value值创建一个关键帧,然后保存在KeyframeSet中
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);
}
总结1:
开发者传入的values目的是为了创建关键帧KeyFrame,最终将传入的所有Value值分装成了关键帧,封装到了KeyFrameSet集合中,根据ofFloat传入属性名称ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);,为每一中属性名创建一个PropertyValuesHolder对象,并且PropertyValuesHolder内部持有KeyFrameSet的对象
回到ValueAnimator的setObjectValues方法,此处想为什么会有mValues == null为空和不为空两种情况了,其实想想在2.3节中的多属性动画可知,当开发者创建了多属性动画时,第创建第二个PropertyValuesHolder对象时,mValues 就不为空了。
public void setObjectValues(Object... values) {
if (values == null || values.length == 0) {
return;
}
//这里的mValues是PropertyValuesHolder数组
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofObject("", null, values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setObjectValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
(2)animator.start();是怎么播放动画的呢?
public void start() {
//为false表示设置动画不反者播放
start(false);
}
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
//可以看到由于默认传入的playBackwards为false,此处以下部分diamagnetic不用关注
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
//表示动画开始、暂停等等属性
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
//动画开始播放时间
mStartTime = -1;
addAnimationCallback(0);
//mStartDelay 延时播放时间,mSeekFraction动画进度跳到的百分比,mReversing是否反向播放
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
//如果跳到的百分比为-1,则从0秒开始播放
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
//设置到需要跳到的播放百分比
setCurrentFraction(mSeekFraction);
}
}
}
public void setCurrentPlayTime(long playTime) {
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
setCurrentFraction(fraction);
}
调用animateValue(currentIterationFraction);
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// Only modify the start time when the animation is running. Seek fraction will ensure
// non-running animations skip to the correct start time.
mStartTime = currentTime - seekTime;
} else {
// If the animation loop hasn't started, or during start delay, the startTime will be
// adjusted once the delay has passed based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
在ValueAnimator中最终回调到onAnimationUpdate,我们添加监听的位置
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
3.2 ObjectAnimator解析
下面这句代码的作用其实和ValueAnimator差不多,通过alpha属性创建PropertyValuesHolder对象,里面保存着0f,1f关键帧的KeyFrameSet集合,KeyFrameSet有持有各关键帧。
ObjectAnimator animator = ObjectAnimator.ofFloat(button,"alpha",0f,1f);
重点关注start方法,可以看到最后一句又回调到ValueAnimator的start方法
@Override
public void start() {
//判断如果当前动画,等待的动画和延迟的动画中有和当前动画相同的动画,就把相同的动画取消掉
AnimationHandler.getInstance().autoCancelBasedOn(this);
if (DBG) {
Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvh = mValues[i];
Log.d(LOG_TAG, " Values[" + i + "]: " +
pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
pvh.mKeyframes.getValue(1));
}
}
//调用父类的start方法
super.start();
}
回到ValueAnimator的start,看到一句startAnimation();重点代码
private void start(boolean playBackwards) {
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
public void setCurrentPlayTime(long playTime) {
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
setCurrentFraction(fraction);
}
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// Only modify the start time when the animation is running. Seek fraction will ensure
// non-running animations skip to the correct start time.
mStartTime = currentTime - seekTime;
} else {
// If the animation loop hasn't started, or during start delay, the startTime will be
// adjusted once the delay has passed based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
经过一系列调用,最终调用到子类ObjectAnimator的animateValue
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up. Note: we allow null target if the
/// target has never been set.
cancel();
return;
}
//先调用父类的animateValue
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}
最终调用到PropertyValuesHolder的setAnimatedValue,这里关注 mSetter.invoke(target, mTmpValueArray);,先说明这句代码的意义:通过反射调用target View的mSetter方法。
void setAnimatedValue(Object target) {
if (mIntProperty != null) {
mIntProperty.setValue(target, mIntAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mIntAnimatedValue);
return;
}
if (mJniSetter != 0) {
nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mIntAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
那么mSetter是怎么来的呢?通过以下这句代码拼接而来的,为了方便理解,我自己写了一个setupSetterOrGetter方法
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
就是通过将属性名的第一个字母大写,然后和"set"和属性名拼接即可
public void setupSetterOrGetter(WeakReference<View> target) {
char firstLetter = Character.toUpperCase(mPropertyName.charAt(0));
String theRest = mPropertyName.substring(1);
String methodName="set"+ firstLetter + theRest;
try {
mSetter = View.class.getMethod(methodName, float.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
总结:也就是从这里可以得出,属性动画最终是通过反射调用Object的各set方法对Object的各属性设置。
3.3 插值器和估值器
第一次学习属性动画时,当听到别人说插值器和估值器感觉是个很复杂、很高大上的东西,其实不然。
其作用就是:插值器负责计算动画执行的百分比,估值器负责根据百分比来计算属性值。插值器计算完动画执行的百分比后,将Fraction交给估值器计算属性的百分比。
(1)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);
}
public interface Interpolator extends TimeInterpolator {
// A new interface, TimeInterpolator, was introduced for the new android.animation
// package. This older Interpolator interface extends TimeInterpolator so that users of
// the new Animator-based animations can use either the old Interpolator implementations or
// new classes that implement TimeInterpolator directly.
}
系统给开发者提供了好几种插值器,现在我们直接看看LinearInterpolator ,这里很容易的理解,getInterpolation的输入和返回成线性比例,开发这也可以自定义插值器,只需要继承Interpolator ,重写getInterpolation。如博主子啊2.2中缩写的input*input,播放速度与时间成平方的关系。
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactory.createLinearInterpolator();
}
}
(2)TypeEvaluator估值器
public interface TypeEvaluator<T> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value.
* @param endValue The end value.
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public T evaluate(float fraction, T startValue, T endValue);
}
系统也给开发者定义了很多估值器,如颜色属性的ArgbEvaluator,float类型的FloatEvaluator
这里直接看看FloatEvaluator的evaluate方法
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>float</code> or
* <code>Float</code>
* @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
//根据属性的起始值+估值器计算出的动画执行百分比*(结束值-起始值)
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
————————————————
版权声明:本文为博主「google忠实粉丝」的原创文章