Android-动画

View Animation

  视图动画也叫补间动画,可以在一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度),譬如如果你有一个T extView对象,你可以移动、旋转、缩放、透明度设置其文本,当然如果它有一个背景图像,背景图像也会随着文本变化。
  视图动画可以通过XML或android代码定义,建议使用xml文件定义,可读性和可重用性更高。Animation抽象类是所有视图动画类的基类,所以基类会提供一些通用的动画属性方法,先来看下视图动画的种类,以及基类提供的方法。

Animation属性方法:
image.png
视图动画的种类:
image.png
  AnimationSet继承自Animation,是对上面四种组合的容器管理类,它没有自己特有的属性,它的属性继承自Animation,所以当我们对set标签使用Animation的属性时,会对该标签下的所有子控件都产生影响。

Alpha属性(渐变透明度)
image.png

Rotate属性(旋转)
image.png

Scale属性(尺寸伸缩)
image.png

Translate属性(移动)

image.png

  视图动画注意点:视图动画执行之后并为改变view的真实布局属性值!譬如有一个button在屏幕的上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的button是没有任何反应的,而点击屏幕上方原来button在的位置却有button点击事件的相应。

Drawable Animation

  Drawable动画其实就是Frame动画(帧动画),它允许你实现像播放幻灯片一样的效果,所以这种动画的xml定义方式文件一般放在res/drawable目录下。我们依旧可以使用xml或java的方式实现帧动画,但依旧推荐使用xml,具体如下:
<animation-list>必须是根节点,包含一个或者多个<item>元素,属性有:
  android:oneshot 为true代表只执行了一次,false循环执行;
  <item>类似一帧的动画资源。
<item>是animation-list的一帧,包含的属性如下:
  android:drawable 一个frame的Drawable资源;
  android:duration一个frame显示多长时间
首先在res/drawable文件夹下新建一个xml文件:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/img1" android:duration="1000" />
    <item android:drawable="@drawable/img2" android:duration="1000" />
    <item android:drawable="@drawable/img3" android:duration="1000" />
    <item android:drawable="@drawable/img4" android:duration="1000" />
</animation-list>

在activity中调用:

pic.setBackgroundResource(R.drawable.animation);
AnimationDrawable animation2 = (AnimationDrawable) pic.getBackground();
animation2.start();

Property Animation

  属性动画机制已经不再是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再是一种视觉上的动画效果了,它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性,所以我们仍然可以将一个view进行移动或缩放,但同时也可以对自定义的view中的point对象进行动画操作,我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去做了。

ValueAnimator

  ValueAnimator是整个属性动画机制中最核心的一个类,属性动画的运行机制是通过不断的对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的,它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需要运行的时长,那么ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。
  最简单的实现:将一个值从0平滑过渡到1,时长300ms:

ValueAnimator vanim = ValueAnimator.ofFloat(0f, 1f);
vanim.setDuration(300);
vanim.start();

  调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数(用来表示从哪过渡到哪再过渡到哪),也许并不需要小数位数的动画过渡,可能只是希望将一个整数值从0平滑过渡到100,那么只要调用ofInt()方法就可以了。通过addUpdateListener()方法来添加一个动画监听器,在动画执行的过程中会不断回调:

ValueAnimator vanim = ValueAnimator.ofFloat(0f, 1f);
vanim.setDuration(300);
vanim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        float value = (float) valueAnimator.getAnimatedValue();
        Log.e("eee", String.valueOf(value));
    }
});
vanim.start();

  除此之外,还可以调用setStartDelay()方法设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环播放包括RESTART和REVERSE两种,分别表示重新播放和倒序播放。

ObjectAnimator

  相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,ValueAnimator只不过是对值进行了一个平滑的动画过渡,但实际使用到这种功能的场景好像并不多,而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,虽说ObjectAnimator会更加常用一些,但它其实是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnimator仍然是属性动画中最核心的一个类。既然是继承关系,那么ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,用法也类似,下面的代码是在5秒内将imageview的透明度从不透明到全透明再恢复到不透明:

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

  同样是调用了ofFloat()方法来创建一个ObjectAnimator的实例,只不过参数有点变化,第一个参数要求传入一个object对象,想要对哪个对象进行动画操作就传入哪个对象,第二个参数是想要对该对象进行的某种动画操作,后面的参数就不固定参数数量了,代表变化的动画效果。通过setDuration设置时长,start()方法启动动画。
  那么对于第二个参数疑问,到底能设定哪些属性呢?是任何值!纳尼?哈哈,慢慢看。
  上面的代码改变的是imageview的alpha属性,那么它有没有这个属性呢,答案是没有,不仅它没有,它的父类也没有,那么问题来了,ObjectAnimator是如何进行操作的呢,其实ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,那么imageview是否含有这两个方法呢,确实有,而且是它的父类view提供的,也就是说任何继承自view的对象都可以进行这项操作。

组合动画

  实现组合动画功能主要需要借助于AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象将会返回一个AnimatorSet.Builder实例,其中包括有以下四个方法:
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有的动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行

ObjectAnimator moveIn = ObjectAnimator.ofFloat(imageView, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();

  上面的代码就实现了:imageview先从屏幕外移动近屏幕,然后开始旋转360°,在旋转的同时进行淡入淡出的操作。

Animator监听器

  Animator类中提供了一个addListener()这个方法,这个方法接收一个AnimatorListener,只要实现这个AnimatorListener就可以监听动画的各种事件了。动画开始、动画结束、动画取消、动画重复。

animSet.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animator) {
        
    }

    @Override
    public void onAnimationEnd(Animator animator) {

    }

    @Override
    public void onAnimationCancel(Animator animator) {

    }

    @Override
    public void onAnimationRepeat(Animator animator) {

    }
});

  很多时候我们并不需要监听那么多个事件,可能只要监听其中一个,那么每次都要将四个接口全部实现一遍就显得非常繁琐,为此Android提供了一个适配器类,叫做AnimatorListenerAdapter,可以自己选择要实现哪个事件。

ValueAnimation的高级用法

  首先了解一下TypeEvaluator的用法

  TypeEvaluator的作用就是告诉动画系统如何从初始值到结束值。之前介绍的ValueAnimator.ofFloat()方法实现的是从初始值到结束值之间的平滑过渡,其实就是在系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过渡到结束值。
image.png
  可以看到FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法,这个方法中传入了三个参数,第一个fraction用于表示动画的完成度,可以根据他来计算当前动画的值应该是多少,第二个和第三个参数分别表示动画的初始值和结束值。那么return的这个值:用结束值减去初始值,算出他们之间的差值,然后乘以fraction这个系数,再加上初始值,表示的就是当前动画的值了。在ofFloat()方法和ofInt()方法中都有对应的值来实现,而在ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的,这个时候我们就要实现一个自己的TypeEvaluator来告知系统如何进行过渡。

① 我们定义一个Point类,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标:

public class Point {
    private float x;

    private float y;

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}

② 接下来定义一个PointEvaluator继承TypeEvaluator并重写evaluate()方法,首先将startValue和endValue强制转换成Point对象,然后同样根据fraction来计算当前动画的x和y值,最后组装到一个新的Point对象中病返回:

public class PointEvaluator implements TypeEvaluator {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
        Point point = new Point(x, y);
        return point;
    }
}

③ 对Point对象进行动画操作,比如有两个Point对象,从startPoint通过动画平滑过渡到endPoint

Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        currentPoint = (Point) animation.getAnimatedValue();
        invalidate();
    }
});
anim.setDuration(5000);
anim.start();

ObjectAnimator的高级用法

  ObjectAnimator的内部工作机制是通过寻找特定属性的get和set方法,然后通过不断地对值进行改变从而实现动画效果,因此要造自定义动画中设定一个color属性,并提供它的get和set方法。

private String color;

public String getColor() {
    return color;
}

public void setColor(String color) {
    this.color = color;
    mPaint.setColor(Color.parseColor(color));
    invalidate();
}

  在setColor()方法中设定画笔颜色为参数传入的参数,并调用invalidate()方法,也即改变画笔颜色之后立即刷新视图。接下来就是借助ObjectAnimator类调用setColor()方法,当然首先要编写一个ColorEvaluator来告知系统怎样进行颜色的过渡。

public class ColorEvaluator implements TypeEvaluator {
    private int mCurrentRed = -1;
    private int mCurrentGreen = -1;
    private int mCurrentBlue = -1;

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        //首先获取到颜色的初始值和结束值
        String startColor = (String) startValue;
        String endColor = (String) endValue;
        //将颜色截取为rgb三个部分
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
        // 初始化颜色的值
        if (mCurrentRed == -1) {
            mCurrentRed = startRed;
        }
        if (mCurrentGreen == -1) {
            mCurrentGreen = startGreen;
        }
        if (mCurrentBlue == -1) {
            mCurrentBlue = startBlue;
        }
        // 计算初始颜色和结束颜色之间的差值
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 将计算出的当前颜色的值组装返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
        return currentColor;
    }

    // 通过fraction计算得到当前的颜色值
    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    // 将十进制的颜色值转换成十六进制
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }
}

结合上一小节介绍的改变位置将两个动画组合在一起(利用AnimatorSet):

ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),
        "#000000", "#FF0000");
anim2.setRepeatCount(2);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim).with(anim2);
animatorSet.setDuration(5000);
animatorSet.start();

实现的效果是:一个黑色的小球从屏幕左上角移动到屏幕右下角,颜色最终变换为红色。
image.png
image.png
image.png

Interpolator

  补间器主要作用是控制动画的变化速率,比如去实现一种非线性运动的动画效果,比如AccelerateInterpolator是一个加速度运动的Interpolator,而DecelerateInterpolator是一个减速运动的Interpolator,而在使用属性动画时,系统默认的Interpolator是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolator,这个属性是可以任意修改的,并且也是可以自定义的。

  不过Interpolator并不是属性动画中新增的技术,补间动画也是支持这个功能的,只不过在属性动画中新增了一个TimeInterpolator接口,这个接口是兼容之前的Interpolator的,这使得所有过去的Interpolator实现类都可以直接拿过来放到属性动画中使用,来看下TimeInterpolator接口的定义:
image.png
  接口中只有一个getInterpolation()方法,在这个方法中接收一个input参数,这个参数的值会随着动画的运行而不断变化,它的变化是非常有规律的,就是根据设定的动画时长匀速的增加,变化范围是0到1,这个input的值决定了fraction的值:input的值系统经过计算后传入到getInterpolation()方法中,而这个方法的返回值就是fraction值了。最简单的匀速变化就是input的值和fraction的值相同,对应LinearInterpolator,来看看源码是不是这么写的:

  哈哈,木有错!那么也就是说,当我们自定义一个Interpolator,只要重写getInterpolation()方法就可以实现自己想要的变化速度。

ViewPropertyAnimator的用法

  其实就是为view的动画操作提供的一种更加便捷的用法,在之前使用ObjectAnimator时(部分属性):

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

  而使用ViewPropertyAnimator来实现同样的效果:

imageView.animate().alpha(0f).setDuration(5000);

  imageView.animate()方法返回的是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,使用连缀的方式就可以将不同的动画效果组合在一起。
注意事项:

  • 整个ViewPropertyAnimator的功能都是建立在view类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后调用的所有方法设置的所有属性都是通过这个实例完成的。
  • 在使用ViewPropertyAnimator时,至始至终都没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动,并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动,当然如果不想使用这种机制的话,也可以显示的调用start方法来启动动画。
  • ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,把所有的功能都串接起来。

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

推荐阅读更多精彩内容