美文网首页
Android 动画机制

Android 动画机制

作者: PuHJ | 来源:发表于2019-03-21 12:29 被阅读0次
    本文大纲 动画知识点

    一、前言

    移动客户端的一个特点就是炫酷的界面,而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引用方式访问

    <?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
    • 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设置好每一张图片的顺序和播放时间

     <?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.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<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还有一些自己定义好的。先介绍下参数:

    • 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 动画机制

          本文链接:https://www.haomeiwen.com/subject/rbzzmqtx.html