Android动画

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

逐帧动画

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

位置:/res/drawable/frame_animation.xml



    
    
    
    

使用逐帧动画时,避免图片较多或图片较大,会引起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




    

    

  • 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接口,故视图插值器可在属性动画中使用。

系统内置的插值器上文已有介绍,系统内置的估值器有: ArgbEvaluator、FloatArrayEvaluator、FloatEvaluator、IntArrayEvaluator、IntEvaluator、PointFEvaluator、RectEvaluator。

如颜色估值器 ArgbEvaluator:

public class ArgbEvaluator implements TypeEvaluator {
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }

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

ArgbEvaluator和颜色有关系的,evaluate函数里面分别对startValue和endValue做了拆分。int总共32位,8位8位的去拆分,分别对应A,R,G,B。 然后对A,R,G,B每个都做fraction转换,在组合到一起形成一个int值。

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

示例1:点击红球落下并弹起。

private final float RADIUS = 70F;
private float circleY = RADIUS;

public void startValueAnimation(){
    circleY = RADIUS;
    
    ValueAnimator anim = ValueAnimator.ofFloat(RADIUS, getHeight()/2-RADIUS);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            circ= (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    anim.setDuration(5000);
    anim.setInterpolator(new BounceInterpolator());
    anim.start();
}

onDraw()方法里重绘:

canvas.drawLine(0f,getHeight()/2,getWidth(),getHeight()/2,paint);//绘制中间横线
canvas.drawCircle(getWidth()/2,circleY,RADIUS,paint);//绘制圆饼

ValueAnimator类针对起始值和结束值进行动画操作,可借助动画进度监听回调,处理一些页面刷新动作。

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):将现有动画与传入的动画并行执行。

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

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。

ViewPropertyAnimator

Android动画团队专门为View的常用属性方法封装的动画操作类,可以链式实现基本动画效果。

//将textview从原有颜色在5秒的时间内渐进到透明,同时缩小0.5倍。
textview.aniate().alpha(0f).scaleX(0.5f).setDruation(5000);

链式调用并行运行多种动画,但不支持更复杂的动画组合(如串式运行动画),适用于单个view的默认属性值的动画。

自定义估值器

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

  • 自定义路径估值器 PathAnimEvaluator
/**
 * 路径估值器
 */
public class PathAnimEvaluator implements TypeEvaluator {
PointModel pointModel = null; 
    /**
     * 动画执行算法
     *
     * @param fraction   表示动画完成度,属性改变的百分比(可以大于1,可以小于0)
     * @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());
        pointModel.setX(x);
        pointModel.setY(y);
        return pointModel;//避免频繁new实例造成内存溢出。
    }
}

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

  • 带动画的控件 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() {
        return pointModel;
    }

    public void setPointModel(PointModel pointModel) {
        this.pointModel = pointModel;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (pointModel == null) {
            pointModel = new PointModel(RADIUS, RADIUS);
            startAnimation();
        } 
        canvas.drawCircle(pointModel.getX(), pointModel.getY(), RADIUS, paint);
    }

    private void startAnimation() {
        //PointModel只是一个带x,y坐标的实体类
        ObjectAnimator translate = ObjectAnimator.ofObject(this,
                "pointModel",
                new PathAnimEvaluator(),
                new PointModel(RADIUS, RADIUS), 
                new PointModel(RADIUS, getHeight() - RADIUS));
        translate.setDuration(10000);
        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) {
        //实现先减速后加速的效果
        float result;
        if (input <= 0.5) {
            result = (float) (Math.sin(Math.PI * input)) / 2;
        } else {
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;
        }
        return result;
    }
}

如上在AnimationView中替换即可。

插值器和估值器的关系

从动画绘制的流程角度来说:

  1. 插值器Interpolator会根据时间流逝的百分比计算出当前属性值改变的百分比,即为TypeEvaluator中的fraction;
  2. 然后估值器TypeEvaluator会根据属性值改变的百分比fraction并结合初始值和结束值,计算出动画当前进度的属性值;
  3. 然后估值器反射调用属性set方法更新属性值,并刷新UI触发onDraw重绘,就实现了动画的效果。

注意事项

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

引用

Android属性动画完全解析(上),初识属性动画的基本用法
自定义控件三部曲之动画篇(七)——ObjectAnimator基本使用
Android属性动画ObjectAnimator源码简单分析

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