概述
前两篇已经讲了属性动画的使用和源码的实现。但是大家应该发现了还有非常重要的一部分没有提及,那就是插值器。无论是在属性动画还是在View动画中,都有一个非常重要的类Interporator,这个类获取到当前的时间进度,然后根据时间进入计算得到我们想要的插值。通俗易懂的语言来说,这就是一个y=f(t)的函数,输入的是时间,输出的是变换后的值。为了自己实现一个属性动画的效果,就先来看下这个重要的类的实现,系统提供的默认Interporator。
插值器
在动画中应用插值器非常简单,在View动画中只要设置setInterpolator就可以了。
outerScaleAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
在属性动画中也可以一样简单地设置:
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "TranslateX", 0.f, 1.f);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
系统提供了蛮多插值器的,如线性插值器,先加速后减速插值器,加速插值器,减速插值器,末尾震荡插值器等。
这些插值器都是从BaseInterpolator继承而来,BaseInterpolator实现了Interpolator接口,Interpolator继承自TimeInterpolator。在TimeInterpolator中,只有一个需要实现的方法。这个方法的意思就是说,我们需要输入0-1.0的表示进度的数,而输出可已超过1.0的表示已经经由此方法转换了的值。
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);
}
BaseInterpolator的代码中,有两个hide的方法,说明不是我们应该调用的,就不去管它。然后看下简单的几种插值器的实现。
首先来看下最简单的线性插值器,可以发现非常简单。getInterpolation方法直接将输入返回。这也非常好理解,既然是线性的,那么就是y=x的这种变换。
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
然后再来看下复杂点的AccelerateInterpolator 。仔细分析下,发现除了在XML布局设置需要的构造器外,逻辑代码非常简单明了。只有一个方法起到了作用
@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Seting
* factor to 1.0f produces a y=x^2 parabola. Increasing factor above
* 1.0f exaggerates the ease-in effect (i.e., it starts even
* slower and ends evens faster)
*/
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
public AccelerateInterpolator(Context context, AttributeSet attrs) {
this(context.getResources(), context.getTheme(), attrs);
}
/** @hide */
public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
}
mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
}
}
看下这个方法,如果我们设置的mFactor等于1的话,那么就是y=x2这个形式的函数。如果不是,那么是y=xfactor的形式。到达终点前会加速趋近于1。
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
上面介绍了Interpolator的用法,但是这只是0-1.0的变换的插值。如何将这个值再次换算到最终显示的值呢。其实在上篇中的源码中也有,但是没有详细讲。如下的代码是animateValue方法,用处是将最后的结果通知调用者。fraction = mInterpolator.getInterpolation(fraction);这个前面已经讲到了。
@CallSuper
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);
}
}
}
然后看下calculateValue这个方法,这个方法中只有两行代码,主要是第一行,getValue中可以通过mEvaluator(当然首先要去设置)去获取最终的值。
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
getValue是抽象的方法,需要子类去实现。setEvaluator也是Keyframes中的抽象方法,除了系统实现的几个外(在PathKeyframes中),我们需要自己定义,然后用setEvaluator设置。
/**
* Sets the TypeEvaluator to be used when calculating animated values. This object
* is required only for Keyframes that are not either IntKeyframes or FloatKeyframes,
* both of which assume their own evaluator to speed up calculations with those primitive
* types.
*
* @param evaluator The TypeEvaluator to be used to calculate animated values.
*/
void setEvaluator(TypeEvaluator evaluator);
系统提供的默认的Evaluator,有Int和float还有颜色属性的Evaluator,也可以自己继承TypeEvaluator去实现。
TypeEvaluator是一个接口,只有一个方法evaluate。我们只要继承就可以了。
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);
}
实现一个自定义的属性动画
我们想实现一个阻尼正震荡效果,频率也是随着时间的增加而上升。为了效果,我们在Interpolator中换算时间,在Evaluator中计算位移的值。
代码如下,只是模拟了一下,并没有真正的按照公式来实现。。
public class ValueAnimatorActivity extends AppCompatActivity {
private static final String TAG = ValueAnimatorActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_value_animator);
}
@Override
protected void onResume() {
super.onResume();
final ImageView view = (ImageView) findViewById(R.id.iv_animation);
if (view != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
performAnimator(v);
}
});
}
}
ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
private void performAnimator(final View view) {
animator.setDuration(1000);
animator.setInterpolator(new MyInterpolator());
animator.setEvaluator(new MyEvaluator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
Log.e(TAG, "value:" + value);
if (view != null) {
view.setTranslationY(value);
}
}
});
animator.start();
}
private class MyInterpolator implements Interpolator {
// 我们需要先慢后快
@Override
public float getInterpolation(float input) {
Log.e(TAG, "input:" + input);
return (float) Math.pow(input, 3);
}
}
private class MyEvaluator implements TypeEvaluator<Float> {
private final float tau = 5.0f;
private final float omiga = 100.0f;
private final float A = 200.0f;
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
double t = Math.exp(-fraction * tau);
double sin = Math.sin(fraction * omiga);
return (float) (A * t * sin);
}
}
}
效果如下,录制时达不到很高的帧率,所以有点失真。
也可以直接用ObjectAnimator来实现,这样就不用自己设置位移了
ObjectAnimator animator = ObjectAnimator.ofFloat(view,"TranslationX",0,100);
animator.setDuration(1000);
animator.setInterpolator(new MyInterpolator());
animator.setEvaluator(new MyEvaluator());
animator.start();
总结
这样我们的属性动画算是介绍完了,属性动画可以做出一些复杂的动画效果,可以实现些有意思的效果。接下去可能会介绍自定义Drawable的实现。