Android 动画机制

2019-03-21  本文已影响0人  PuHJ
本文大纲 动画知识点

一、前言

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

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

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

二、视图动画

1)、补间动画

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

下面以TranslateAnimation 动画为例举例:

①、XML方式实现

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

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0" 
    android:fromYDelta="0"
    android:toXDelta="200"
    android:toYDelta="200"
    android:duration="2000"
    android:fillBefore="true">
</translate>

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
问题:补间动画只是更改控件的内容

直接通过代码分析,通过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设置好每一张图片的顺序和播放时间

 <?xml version="1.0" encoding="utf-8"?>
 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"  >
     <!-- 定义一个动画帧,Drawable为img0,持续时间50毫秒 -->
    <item android:drawable="@drawable/img0" android:duration="50" />
 </animation-list>

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对象

使用的方法不同,最终返回值得类型也不一致,但使用方法都是一样的。但重点说下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<T> {

    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 <T> ObjectAnimator(T target, Property<T, ?> 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<MainActivity, Object> property = new Property<MainActivity, Object>(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还有一些自己定义好的。先介绍下参数:

着重看下PropertyValuesHolder
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();
    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流程

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

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

}
上一篇 下一篇

猜你喜欢

热点阅读