Android应用开发三部曲 --- 动画

目录

1、前言
2、属性动画
3、插值器与估值器
4、其它动画方式
5、源码走读

前言

android中有三种类型的动画,帧动画、补间动画以及属性动画。

  • 帧动画,原理类似于电影放映,将很多张图片顺序播放,形成动画效果
  • 补间动画,继承自Animation,常通过xml描述动画,实现缩放、旋转、平移等
  • 属性动画,使用最多的动画形式,也是最灵活的动画形式。

因为帧动画较为简单,本文不做介绍。补间动画功能较为强大,但自身有两个方面的缺陷导致全面不敌属性动画。

  • 补间动画的只能作用到View上
  • 补间动画的效果有问题,例如view平移,但view的点击区域仍然在原处,非常影响用户体验

属性动画

属性动画的使用方式较简单,非常灵活。属性动画最常用的就是ValueAnimator和ObjectAnimator。

 ValueAnimator animator = ValueAnimator.ofFloat(1f, 0.5f);
 animator.setDuration(1500);
 animator.addUpdateListener(new AnimatorUpdateListener() {
     public void onAnimationUpdate(ValueAnimator animation) {
         float alpha = (Float) animation.getAnimatedValue();
         mAniTextView.setAlpha(alpha);
     }
 });
 animator.start();

ObjectAnimator animator = ObjectAnimator.ofFloat(mAniTextView, "alpha", 1f, 0.5f);
animator.setDuration(1500);
animator.start();

ValueAnimator,需要根据AnimatorUpdateListener的回调返回值实现动画。

ObjectAnimator使用方式更简单一点,不需要写监听回调也能实现,但ObjectAnimator操纵的属性一定要有get和set方法才行,否则动画无效。例如上述代码中,view中有setAlpha和getAlpha方法的。

插值器与估值器

插值器,即Interpolator,常见如线性插值器LinearInterpolator等。插值器的作用在于,动画执行过程中,存在表征动画执行情况的系数 input,从0到1,input默认是线性增长,插值器改变input的增长方式,但初始值和终值一定不变。

插值器必须实现接口TimeInterpolator,重写getInterpolation方法。

  float getInterpolation(float input);

LinearInterpolator,线性插值器,它的getInterpolation非常简单,将input值返回即可,因此它是线性的

 public float getInterpolation(float input) {
    return input;
}

BounceInterpolator,Bounce有反弹跳跃的意思,它的返回值非常有意思,当返回值为接近于1时,它会反弹减小再增加,往复几次,最终返回值为1。可以想象有一个弹力球从空中掉落,当它落地时它会反弹几次最终才静止下来。

  public float getInterpolation(float t) {
    // _b(t) = t * t * 8
    // bs(t) = _b(t) for t < 0.3535
    // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
    // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
    // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
    // b(t) = bs(t * 1.1226)
    t *= 1.1226f;
    if (t < 0.3535f) return bounce(t);
    else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
    else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
    else return bounce(t - 1.0435f) + 0.95f;
}

BounceInterpolator的getInterpolation方法如上,其实它就是某个数学公式的描述,如果想要自定义插值器,重写getInterpolation即可,记住,初始值和终值必须为0和1。

插值器用于计算表征动画执行情况系数,那么估值器的作用是什么呢?以前文中alpha动画为例,插值器返回的值是【01】,但alpha的值却是【10.5】,显然不是拿插值器返回值直接使用的,估值器即是根据插值器返回值,计算动画最终参数值。

估值器必须实现TypeEvaluator接口,重写evaluate方法。

  public T evaluate(float fraction, T startValue, T endValue);

常见的估值器有FloatEvaluator,IntEvaluator等,前文代码中的alpha动画传入的数值是float型,所以使用的估值器为FloatEvaluator,查看下FloatEvaluator的代码

  public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}

fraction即为插值器的返回值,FloatEvaluator的逻辑非常简单,线性变化而已。一般估值器都较为简单,不像插值器一样复杂,目前只遇到过一类较复杂的估值器,颜色变换。因为颜色是由argb四个分量组成,需要对每个分量线性增长再合成,具体逻辑不细说了,读者可自行完成。

其它动画方式

如果从更宏观的角度看动画,除了在前言中谈到的三种动画形式,还有其它方法也能实现。

动态壁纸是一种动画吗?从宏观角度看,它确实也是一种动画。动态壁纸原理非常简单,不停地绘制,不停地刷新,壁纸上的元素永远在变化,所以才有动态的效果。本博中Camera与Matrix的那些事儿使用的3D容器,在滑动时也会产生动画效果。

属性动画的根本原理,也是改变了View的某些属性,比如alpha值,或者TranslationY值等等,再来刷新界面,重绘产生了动画效果。

所有动画的原理均是改变了绘制元素的属性,重绘时产生了动画效果。当某此场景中属性动画已无法完成时,可考虑绘制时动画

根据个人经验,凡是有跟手的动画效果,都是绘制时动画形式完成的,例如Launcher中滑屏效果等

源码走读

ValueAnimator、ObjectAnimator的原理是什么呢?它们的调用流程是什么呢?

Android应用开发三部曲 --- 动画_第1张图片
Paste_Image.png

结合调用时序图,最关键的步骤是PropertyValuesHolder的calculateValue方法,此方法中调用插值器和估值器计算动画参数,并返回给ValueAnimator,ValueAnimator再调用监听器的onAnimationUpdate方法,将参数返回。

ObjectAnimator继承自ValueAnimator,大体上逻辑一致,但ObjectAnimator最终是通过反射,调用属性的set方法,设置view属性,达到动画的目的。那么反射调用在哪呢?

@Override
void 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);
    }
}

ObjectAnimator重写animateValue方法,调用完父类方法后,还有如下逻辑:

  mValues[i].setAnimatedValue(target);

查看它的具体实现

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

原来反射调用就是在此处实现的。mSetter.invoke,这一看就是反射调用呐。

具体细节不再阐述,读源码就得不求甚解,否则细节会抠死人。

你可能感兴趣的:(Android应用开发三部曲 --- 动画)