Android UI-属性动画(三)

概述

前两篇已经讲了属性动画的使用和源码的实现。但是大家应该发现了还有非常重要的一部分没有提及,那就是插值器。无论是在属性动画还是在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();

系统提供了蛮多插值器的,如线性插值器,先加速后减速插值器,加速插值器,减速插值器,末尾震荡插值器等。


image.png

这些插值器都是从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去实现。


image.png

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

效果如下,录制时达不到很高的帧率,所以有点失真。


1.gif

也可以直接用ObjectAnimator来实现,这样就不用自己设置位移了

ObjectAnimator animator = ObjectAnimator.ofFloat(view,"TranslationX",0,100);
animator.setDuration(1000);
animator.setInterpolator(new MyInterpolator());
animator.setEvaluator(new MyEvaluator());
animator.start();

总结

这样我们的属性动画算是介绍完了,属性动画可以做出一些复杂的动画效果,可以实现些有意思的效果。接下去可能会介绍自定义Drawable的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,580评论 0 9
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 1,899评论 0 24
  • 恋上一座城,大抵是因为这座城有你爱的人,有你的亲人。从一座城到另一座城,无数次,在火车的轰鸣声中丈量着他们的距离。...
    寒塘鹤影_阅读 561评论 0 2
  • 一顾倾人城。再顾倾人国。宁不知倾城与倾国。佳人难再得。 说到气质,想起来这首诗歌。 每个人都有自己与众不同的气质,...
    羽扇纶巾_Q酱阅读 119评论 3 4