Android动画

作者: 正规程序员 | 来源:发表于2019-06-16 23:06 被阅读13次

    当前Android应用开发涉及的动画主要有三种,分别是:视图动画,逐帧动画,属性动画。

    逐帧动画

    是在 xml 中定义好一系列图片之后,使用AnimationDrawable来顺序播放的动画。

    位置:/res/drawable/frame_animation.xml

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
        <item android:drawable="@drawable/item" android:duration="100"/>
        <item android:drawable="@drawable/item" android:duration="100"/>
        <item android:drawable="@drawable/item" android:duration="100"/>
        <item android:drawable="@drawable/item" android:duration="100"/>
    </animation-list>
    

    使用逐帧动画时,避免图片较多或图片较大,会引起OOM。

    ImageView imageView = findViewById(R.id.image);
    imageView .setBackgroundResource(R.drawable.rocket_thrust);
    
    AnimationDrawable rocketAnimation = 
                (AnimationDrawable)imageView.getBackground();
    rocketAnimation.start();//不可在onCreate中执行,要等view首次绘制完毕。
    

    视图动画

    针对View的影像进行缩放、透明度渐变、旋转、平移或组合使用,从而产生动画的效果。

    Java类名 xml关键字 描述
    AlphaAnimation < alpha > 渐进透明度动画效果
    RotateAnimation < rotate > 旋转动画效果
    ScaleAnimation < scale > 渐进尺寸伸缩动画效果
    TranslateAnimation < translate > 平移动画效果
    AnimationSet < set > 组合其他动画元素或set元素的容器

    位置:res/anim/view_anim.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="3000"
        android:fillAfter="true"
        android:shareInterpolator="true">
    
        <rotate
            android:fromDegrees="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="360" />
    
        <alpha
            android:duration="200"
            android:fillAfter="true"
            android:fromAlpha="0"
            android:interpolator="@android:anim/linear_interpolator"
            android:repeatCount="-1"
            android:repeatMode="reverse"
            android:toAlpha="1" />
    </set>
    
    • duration:动画持续时间;
    • fillAfter:动画结束时,是否保持最后一帧;
    • repeatCount:动画循环的次数,默认0次不循环,-1为无线循环;
    • repeatMode:动画循环模式,reverse指从动画结束处循环,restart指从动画开始处循环。
    • interpolator:插值器,控制动画执行的速度。
    • shareInterpolator:是否与set容器中其他动画元素共享插值器,false为各自使用自己的插值器。
    • fromDegrees:旋转动画起始的角度,单位度,浮点值。
    • pivotX:旋转中心的X坐标,n%表示相对于自身左边缘的 自身宽度的n%。
    Animation animation= AnimationUtils.loadAnimation(this, R.anim.view_anim);
    button.startAnimation(animation);
    

    插值器
    Interpolator是Animation类的一个xml属性,规定了从初始值过渡到结束值得渐变规律,视图动画中alpha、scale、rotate、translate、set动画元素都会继承此属性。

    如下是系统内置的插值器实现:

    插值器类名 Resource ID 描述
    AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 系统默认插值器。在动画开始与结束时速度比较慢,在中间的时候加速。
    AccelerateInterpolator @android:anim/accelerate_interpolator 在动画开始的地方速度比较慢,然后开始加速。
    AnticipateInterpolator @android:anim/anticipate_interpolator 开始的时候向后然后向前甩。
    AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 开始的时候向后然后向前甩一定值后返回最后的值。
    BounceInterpolator @android:anim/bounce_interpolator 动画结束的时候弹起。
    CycleInterpolator @android:anim/cycle_interpolator 动画循环播放特定的次数,速度沿着正弦曲线。
    DecelerateInterpolator @android:anim/decelerate_interpolator 在动画开始的地方快然后慢。
    LinearInterpolator @android:anim/linear_interpolator 常量速度。
    OvershootInterpolator @android:anim/overshoot_interpolator 向前甩一定值后再回到原来位置。
    PathInterpolator @android:anim/path_interpolator 新增,按照定义坐标路径动画。

    属性动画

    利用插值器和估值器,来计算出各个时刻 View 的属性,然后通过改变 View 的属性来实现 View 的动画效果。

    • 时间插值器(Interpolator):作用是根据时间的流逝的百分比来计算属性改变的百分比。
    • 类型估值器(TypeEvaluator):根据当前属性改变的百分比来计算改变后的属性值。

    属性动画中的TimeInterpolator插值器兼容Interpolator接口,故视图插值器可在属性动画中使用。

    ValueAnimator:通过从初始值到结束值得平滑过渡实现动画效果。addUpdateListener可监听动画执行过程。

    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    anim.setDuration(5000);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float currentValue = (float) animation.getAnimatedValue();
            Log.d("TAG", "cuurent value is " + currentValue);
        }
    });
    anim.start();
    

    ObjectAnimator:ObjectAnimator继承ValueAnimator,可以对任意对象的属性方法进行操作,达到动画效果。

    ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
    animator.setDuration(2000);
    animator.start();
    

    第一个参数表示动画目标对象,第二个参数表示对象属性名,后面的参数不固定,表示动画的初始和结束值。

    组合动画:实际开发需求中,一般需要用到组合动画而不是单一动画。属性动画的组合借助AnimatotSet类实现。

    • play(Animator anim):执行现有动画。
    • after(Animator anim):将现有动画排在传入的动画之后执行。
    • after(Animator anim):将现有动画延迟指定毫秒后执行。
    • before(Animator anim):将现有动画排在传入的动画之前执行。
    • with(Animator anim):将现有动画与传入的动画并行执行。

    示例 1:先执行缩放动画,再并行执行旋转、平移、透明度动画。

    ObjectAnimator scaleX = ObjectAnimator.ofFloat(textview, "scaleX", 1f, 0.5f, 1f);
    ObjectAnimator alpha = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0.5f, 1f);
    ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
    ObjectAnimator translationX = ObjectAnimator.ofFloat(textview, "translationX", 
            textview.getTranslationX(), textview.getTranslationX()-150f);
            
    AnimatorSet animSet = new AnimatorSet();
    animSet.play(rotate).with(translationX).with(alpha).after(scaleX);
    animSet.setDuration(3000);
    //监听动画状态
    animSet.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            //动画结束
        }
    });
    //监听动画进度
    animSet.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }
    });
    animSet.start();
    

    translationX是View左上角相对父容器左上角在X轴的偏移量,translationX默认值为0。

    自定义估值器

    属性动画的自定义估值器需要实现TypeEvaluator接口,并复写evaluate()方法。

    • 自定义路径估值器 PathAnimEvaluator
    /**
     * 路径估值器
     */
    public class PathAnimEvaluator implements TypeEvaluator<PointModel> {
        PointModel pointModel = null;
    
        public PathAnimEvaluator(PointModel pointModel) {
            this.pointModel = pointModel;
        }
    
        /**
         * 动画执行算法
         *
         * @param fraction   表示动画完成度,属性改变的百分比
         * @param startValue 动画初始值
         * @param endValue   动画结束值
         * @return 当前动画过渡值
         */
        @Override
        public PointModel evaluate(float fraction, PointModel startValue, PointModel endValue) {
            //当前进度的x坐标 = 初始值+动画完成百分比*(结束值-初始值)。
            float x = startValue.getX() + fraction * (endValue.getX() - startValue.getX());
            float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY());
            Log.d("TestActvity", "fraction=" + fraction);
    
            pointModel.setX(x);
            pointModel.setY(y);
            return pointModel;//避免造成非常多的内存碎片
        }
    }
    

    从上述示例可知,对于任何实例对象,只要赋给他属性方法,并提供初始值和结束值,都可以实现从初始值到结束值的平滑过渡。

    • 带动画的控件 AnimationView:从起点到终点,圆饼滑动。
    public class AnimationView extends View {
        private final float RADIUS = 100f;
    
        //属性名
        private PointModel pointModel;
        private Paint paint;
    
        public AnimationView(Context context) {
            super(context);
            init(context, null, 0);
        }
    
        public AnimationView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs, 0);
        }
    
        public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs, defStyleAttr);
        }
    
        private void init(Context context, AttributeSet attrs, int defStyleAttr) {
            paint = new Paint();
            paint.setColor(Color.RED);
        }
    
        public PointModel getPointModel() {
            Log.d("TestActvity", "getPointModel");
            return pointModel;
        }
    
        public void setPointModel(PointModel pointModel) {
            this.pointModel = pointModel;
            invalidate();
            Log.d("TestActvity", "setPointModel="+pointModel.getY());
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (pointModel == null) {
                pointModel = new PointModel(RADIUS, RADIUS);
                canvas.drawCircle(pointModel.getX(), pointModel.getY(), RADIUS, paint);
                startAnimation();
            } else {
                canvas.drawCircle(pointModel.getX(), pointModel.getY(), RADIUS, paint);
            }
        }
    
        private void startAnimation() {
            //PointModel只是一个带x,y坐标的实体类
            PointModel startPoint = new PointModel(RADIUS, RADIUS);
            ObjectAnimator translate = ObjectAnimator.ofObject(this,
                    "pointModel",
                    new PathAnimEvaluator(startPoint),
                    startPoint, new PointModel(RADIUS, getHeight() - RADIUS));
    
            translate.setDuration(10000);
            //TODO 动画结束时弹跳效果未生效???
            translate.setInterpolator(new BounceInterpolator());
            translate.start();
        }
    }
    

    动画在执行过程中会多次反射调用setPointModel()方法并赋新值,在这里需要添加触发UI刷新的操作,确保动画有效。

    如果动画没有初始值,那么就会使用get方法提供的初始值,若还没有则会使用该类型的系统默认值或者报错。

    自定义插值器

    属性动画自定义插值器需实现TimeInterpolator接口,并复写getInterpolation()方法。

    从上文自定义估值器可知,fraction表示动画作用对象的属性完成的百分比,其由系统调用插值器的getInterpolation()方法获得。

    • 自定义速率插值器 PathAnimInterpolator
    public class PathAnimInterpolator implements TimeInterpolator {
        @Override
        public float getInterpolation(float input) {
            //动画开始时,input值 = 0;动画结束时input = 1
    
            //实现先减速后加速的效果
            float result;
            if (input <= 0.5) {
                result = (float) (Math.sin(Math.PI * input)) / 2;
            } else {
                result = (float) (2 - Math.sin(Math.PI * input)) / 2;
            }
    
            Log.d("TestActvity", "result=" + result);
            return result;
        }
    }
    

    如上在AnimationView中替换即可。

    注意事项:

    • 在activity销毁的时候,一定确保动画关闭,资源回收,避免内存泄露。
    • 视图动画不能改变view的属性,只是对其影像做动画,需要view.clearAnimation()后方可正常操作其属性。
    • 逐帧动画避免图片过大、过多,容易造成内存泄露。
    • 开启硬件加速,可以提升动画的流畅性。
    • 动画操作里,尽量用dp,而不是px,处理好屏幕适配问题。

    相关文章

      网友评论

        本文标题:Android动画

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