Android动画

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

系统内置的插值器上文已有介绍,系统内置的估值器有: 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 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源码简单分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,119评论 1 38
  • Animation Animation类是所有动画(scale、alpha、translate、rotate)的基...
    四月一号阅读 1,901评论 0 10
  • 本文主要是针对android 中的动画进行详细描述,并简单分析原理;一、概述Android动画分为三种:帧动画(F...
    暮染1阅读 773评论 0 0
  • 动画基础概念 动画分类 Android 中动画分为两种,一种是 Tween 动画、还有一种是 Frame 动画。 ...
    Rtia阅读 1,223评论 0 6
  • 东营市海河小学2018级3班“自立孝亲,读书修身”活动李思璐读书,练字打卡第42天
    a49078996e46阅读 75评论 0 0