Android 动画机制

本文大纲
动画知识点

一、前言

移动客户端的一个特点就是炫酷的界面,而Android中的界面都是View,所以要想实现性能高且好看的界面,就必须需要学会自定义VIew。动画机制操作的对象就是View,动画是自定义View的一个技术点。简而言之,Android中的动画分为两大类。

第一类:称之为视图动画。

  • Tween Animation:补间动画,通过平移(TranslateAnimation)、缩放(ScaleAnimation)、旋转(RotateAnimation)、透明度(AlphaAnimation)四个子类实现的动画,或是其相对应的XML方式实现的动画称之为补间动画。补间动画只需设定初始状态和结束状态,中间的状态有系统计算而得出的。
  • Frame Animation:逐帧动画,即按序播放一组预先定义好的图片,实现起来比较简单,但大量的图片和快速的切换会导致oom内存溢出。

第二类:称之为属性动画。
顾名思义,属性动画即更改控件的View属性来完成,而补间动画是更改外在内容位置。

  • ValueAnimator:ValueAnimator更像一个Map,利用时间估值器获得当前的数值。根据这个数据再更新当前View的属性值。
  • ObjectAnimation:ObjectAnimator是ValueAnimator的子类,ObjectAnimation中就可以直接控制View的属性。

二、视图动画

1)、补间动画

补件动画共有四种类型,分别是:

  • TranslateAnimation 位移动画
  • ScaleAnimation 缩放动画
  • RotateAnimation 旋转动画
  • AlphaAnimation 透明度改变动画
  • AnimationSet 补间动画四种动画组合

下面以TranslateAnimation 动画为例举例:

①、XML方式实现

1、首先在res/anim中构建一个XML文件,采用R.anim.XXXX ID引用方式访问




2、第二步,自然是加载动画,让View控件和Animation绑定
系统在AnimationUtils类中提供了public static Animation loadAnimation(Context context, @AnimRes int id)方法,用于加载XML动画。

loadAnimation中首先调用XML解析器将XML布局解析成Animation类。然后在控件中startAnimation,即可开启动画。

Animation animation = AnimationUtils.loadAnimation(this, R.anim.xxx);
// 开启动画
view.startAnimation(animation);
②、Java代码方式实现

代码方式和XML方式没有本质区别。唯一的不同点在于,Animation的来源不一样,一个来自XML布局,另一个在代码中写死。直接看下代码:

        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.linear);
        final TranslateAnimation translateAnim =
                new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400
                        ,Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400);

        translateAnim.setDuration(1000);
        translateAnim.setFillAfter(true);
        linearLayout.startAnimation(translateAnim);

通过构造函数,构造一个TranslateAnimation,也可以通过TranslateAnimation方法设置一些执行属性。

Animation
  • setDuration : 设置动画执行时间
  • setInterpolator : 设置插值器,配合时间估值器,更改值
  • setRepeatMode : 设置重复模式,是否立即停止
  • setRepeatCount : 重复次数
  • setFillBefore : 是否结束后,停在开始界面
  • setFillAfter : 是否结束后,停在最后界面
  • setAnimationListener : 设置动画监听器
问题:补间动画只是更改控件的内容

直接通过代码分析,通过Animation让TextView移动(400,400),再点击View现在的位置不会触发View的点击事件,而点击View原位置,会触发点击事件,弹出Toast。

补间动画并没有改变控件内部的属性值,因为动画是ParentView不断调整 ChildView 的画布坐标系来实现的。他没有改变layout摆放的位置,只是在原始的位置上,加上相应的偏移量实现的。故此点击最后的位置,不会被响应。

        TextView  textView = (TextView) findViewById(R.id.text);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(),"Toast",Toast.LENGTH_SHORT).show();
            }
        });

        final TranslateAnimation translateAnim =
                new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400
                        ,Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400);

        translateAnim.setDuration(1000);
        translateAnim.setFillAfter(true);

        textView.startAnimation(translateAnim);

2)、逐帧动画

逐帧动画和补间动画的XML方式有点类似,但不同点是补间动画只需要设置一头一尾的状态,中间的状态是有系统计算的,而逐帧动画则每一帧都是一张图片。

用例:

1、先在XML设置好每一张图片的顺序和播放时间

 
 
     
    
 

2、将XML转换成AnimationDrawable对象,并设置给View即可(setBackgroundDrawable)

 // 通过逐帧动画的资源文件获得AnimationDrawable示例
 frameAnim=(AnimationDrawable) getResources().getDrawable(R.drawable.bullet_anim);
 // 把AnimationDrawable设置为ImageView的背景
 view.setBackgroundDrawable(frameAnim);

二、属性动画

1)、ValueAnimator

老规矩,直接看Demo

public class MainActivity extends AppCompatActivity {

    private final static String TAG = MainActivity.class.getSimpleName();
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text);

        //构造一个ValueAnimator对象
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
        // 动画持续时间
        valueAnimator.setDuration(3000);
        // 动画持续次数 -1代表一致重复
        valueAnimator.setRepeatCount(-1);
        // 动画重复模式 分为两种 从头开始 从尾开始
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        // 动画状态的监听
        valueAnimator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                Log.e(TAG, "onAnimationStart: " );
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                Log.e(TAG, "onAnimationEnd: " );
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                Log.e(TAG, "onAnimationCancel: " );
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                Log.e(TAG, "onAnimationRepeat: " );
            }
        });

        // 动画数值更新回调接口,这个数值从0开始到100
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                textView.layout(value, value, value + textView.getWidth()
                        , value + textView.getHeight());
            }
        });
    }
}

    @Override
    public void onAttachedToWindow() {
        // 开始执行
        valueAnimator.start();
    }

    @Override
    public void onDetachedFromWindow() {
        // 取消执行
        valueAnimator.cancel();
    }

从上面代码可以看出,用法类似于Animation。而ValueAnimatior的用法是在一定的时间内,匀速的回调当前时间的数值。对于上例可以理解为,加速度a = 100/3000。初始值为0,结束值为100。在t时刻,值为多少。

当然这个是有点特别,没有插值器。简单理解插值器就是改变加速度a的值得东西,其他的求法和用法都一致。

最终在AnimatorUpdateListener回调中,可以得到当前的值,根据这个值,再对View进行布局或者其他的操作。

两个回调:

在ValueAnimatior中有两个回调监听,一个是回调动画状态的,另一个是返回当前时刻的值得。

      // 动画状态的监听
      valueAnimator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
                Log.e(TAG, "onAnimationStart: " );
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                Log.e(TAG, "onAnimationEnd: " );
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                Log.e(TAG, "onAnimationCancel: " );
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                Log.e(TAG, "onAnimationRepeat: " );
            }
        });

        // 动画数值更新回调接口,这个数值从0开始到100
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                textView.layout(value, value, value + textView.getWidth()
                        , value + textView.getHeight());
            }
        });
    }
}
  • valueAnimator.removeAllUpdateListeners();可以移除所有的监听对象。
  • valueAnimator.pause(); 暂停动画 API 19 以上
  • valueAnimator.isPaused(); 判断是否暂停中 API 19 以上
  • valueAnimator.isRunning(); 判断是否正在运行
  • valueAnimator.isStarted(); 判断是否调用start()
生成ValueAnimator对象
  • 构造方法
    一般不会使用无参构造方法
    public ValueAnimator() {
    }

  • 静态方法生成
    主要有以下几类:
    1、ValueAnimator.ofInt(float... values);
    2、ValueAnimator.ofFloat(float... values);
    3、ValueAnimator.ofArgb(float... values); API21以上才有
    4、ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values);

使用的方法不同,最终返回值得类型也不一致,但使用方法都是一样的。但重点说下ofArgb和ofObject。其中ofArgb是ofObject的一种特例。

先来看下ValueAnimator.ofArgb(float... values);

 public static ValueAnimator ofArgb(int... values) {
//1、生成对象
        ValueAnimator anim = new ValueAnimator();
// 2、存放关键值
        anim.setIntValues(values);
// 3、设置计算器Evaluator
        anim.setEvaluator(ArgbEvaluator.getInstance());
        return anim;
    }

其中 anim.setEvaluator(ArgbEvaluator.getInstance());是最关键的一部,他需要自定义TypeEvaluator

下面就是TypeEvaluator的接口,它是通过一定的规则,返回当前时间的值,回调给用户使用。

public interface TypeEvaluator {

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

}

下面看下ArgbEvaluator.getInstance()代码:
颜色是ARGB,为32位,每一个通道8bit。fraction代表当前的百分值,通过fraction算出当前准确的颜色并返回。其中fraction是根据你传入的时间和当前的时间和插值器算出的一个值。

public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                (int)((startB + (int)(fraction * (endB - startB))));
    }

ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values);方法含有两部分,一个是传入TypeEvaluator ,另一个传入关键的数值。

2)、ObjectAnimator

 * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
 * The constructors of this class take parameters to define the target object that will be animated
 * as well as the name of the property that will be animated. Appropriate set/get functions
 * are then determined internally and the animation will call these functions as necessary to
 * animate the property.

解释:ObjectAnimator的作用就是对target控件提供属性动画。他需要传递一个target和更改的属性值。且target中提供的属性,需要有对应的setXXX和getXXX方法,这样才能通过反射执行方法,并将值传递过去。最终更改target的属性。

ObjectAnimator是继承自ValueAnimator,所有的ValueAnimator中的可用方法和变量,在ObjectAnimator都可以用。

在ValueAnimator介绍中,可以得出ValueAnimator并不会直接的和View控件绑定,它只会根据时间消逝的进度,得到一个有用的当前时刻对应的值,用户需要自己根据此值做出相应的动作。所有ObjectAnimator就对此进行了完善,在ObjectAnimator中就可以控制View控件的动画。

①、ObjectAnimator例子
        TextView  textView = (TextView) findViewById(R.id.text);
        ObjectAnimator objectAnimator = ObjectAnimator
                .ofFloat(textView,"textSize",10,30);

        objectAnimator.setDuration(10000);
        objectAnimator.start();

上述的例子中,在10S内,View的TextSize从10到30匀速变化。其中textView就是target,textSize为需要更改的属性方法后后半截方法名。10,30是大小变化的范围。

最终的执行会到达PropertyValuesHolder中的setAnimatedValue方法。

  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对象
1、构造方法:
    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }

    private  ObjectAnimator(T target, Property property) {
        setTarget(target);
        setProperty(property);
    }

这两个构造方法,一个传入的是String类型的propertyName,另一个是Property对象。

现在就看下用自定义Property的demo,通过动画给一个Activity的设置背景颜色。

Property的类中提供了两个必须重写的方法set/get方法。从文档中可看出,传递propertyName、Property都行。但是有些情况下,对待一个私有的属性,并不能提供相应的get/set方法,就需要使用Property了,来达到移花接木的作用。既然自己不能对外提供接口,那再给个方式(Property),你重新我的接口就行了。

    // Drawable
    private ColorDrawable mBgDrawable;

    /**
     * 给背景设置一个动画
     *
     * @param endProgress 动画的结束进度
     * @param endCallback 动画结束时触发
     */
    private void startAnim(float endProgress, final Runnable endCallback) {
        // 获取一个最终的颜色
        int finalColor = Resource.Color.WHITE; // UiCompat.getColor(getResources(), R.color.white);
        // 运算当前进度的颜色
        ArgbEvaluator evaluator = new ArgbEvaluator();
        // 得到endProgress时刻的颜色
        int endColor = (int) evaluator.evaluate(endProgress, mBgDrawable.getColor(), finalColor);
        // 构建一个属性动画
        ValueAnimator valueAnimator = ObjectAnimator.ofObject(this, property, evaluator, endColor);
        valueAnimator.setDuration(1000); // 时间
        valueAnimator.setIntValues(mBgDrawable.getColor(), endColor); // 开始结束值
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                // 结束时触发
                endCallback.run();
            }
        });
        valueAnimator.start();
    }

    // 自定义Property
    private final Property property = new Property(Object.class, "color") {
        @Override
        public void set(MainActivity object, Object value) {
            object.mBgDrawable.setColor((Integer) value);
        }

        @Override
        public Object get(MainActivity object) {
            return object.mBgDrawable.getColor();
        }
    };
2、ofXXX方式

除了继承ValueAnimator的方法外,ObjectAnimator还有一些自己定义好的。先介绍下参数:

  • target :被作用的对象
  • TypeEvaluator : 计算器,计算当期时刻的值
  • TypeConverter :转换器,动画中的值类型和属性类型不同时。如属性是int,值类型是float
  • PropertyValuesHolder : 动画需要的一些参数的封装类,最终都会生成该对象
着重看下PropertyValuesHolder
  • 例子
    生成旋转的动画rotationHolder和改变背景颜色的colorHolder的组合动画。最后再ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);生成对应的ObjectAnimator的对象。
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", -20f, 20f, 10f, -10f, 0f);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff, 0xffff00ff,  0xffffffff);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);
animator.setDuration(1000);
animator.start();
  • PropertyValuesHolder代码分析
    String mPropertyName;  // 属性名
    protected Property mProperty;  // 传入的mProperty对象
    Method mSetter = null;   // 通过反射执行的set方法Method 
    private Method mGetter = null;// 通过反射执行的get方法Method  
    private TypeEvaluator mEvaluator; // 传入的数值计算器
    private Object mAnimatedValue;   // 动画的数值
    private TypeConverter mConverter;  // 类型转换器

在PropertyValuesHolder里面,存储了用户传入的参数,并提供了相应的方法,如生成textSize属性的set/get方法,和执行该属性的反射方法。

三、属性动画原理

1)、属性动画流程

Value和ObjectAnimator流程
  • 第一步,传入相应的参数,如ObjectAnimator.ofFloat(textView,"textSize",10,30);生成一个ValueAnimator对象
  • 第二步,根据传入的setDuration执行时间,和插值器策略一同算出当前时刻的百分比
  • 第三步,根据计算出的fraction值,算出此刻的value是start和end中间的哪一个。

Value和ObjectAnimator前三步大体相似,后面轻微的不同。

  • ValueAnimator是将算出的值通过用户注册的AnimatorUpdateListener接口回调给使用者
  • ObjectAnimator是根据反射执行target中的set方法,并将当前值传递。在setXXX中可以重新绘制

2)、插值器

常见的插值器:

插值器种类 描述
LinearInterpolator 线性插入器,默认的插值器,匀速变化
BounceInterpolator 动画结束的时候弹起
AccelerateInterpolator 动画开始的地方速率改变比较慢,然后开始加速
DecelerateInterpolator 开始的地方快然后慢

Android的动画中,即使没有设置Interpolator,也会有个默认的插值器LinearInterpolator。插值器会将时间变化率转化成fraction的一个规则。

所有的插值器都会继承自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);
}
LinearInterpolator代码

代码中他会将时间百分比直接的返回。自己可以自定义差值器,主要的工作就是根据传入的input,映射成为另外的一个float值,即可。

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    // 将传入的input值,原路返回,就是默认的、匀速的插值器了
    public float getInterpolation(float input) {
        return input;
    }

}

你可能感兴趣的:(Android 动画机制)