Android帧、补间、属性动画

1. 分类

动画必不可少,因为它可以让交互更加流畅、自然,增强用户满意度。在Android中动画主要有以下4类:

  • 帧动画
  • 补间动画
  • 属性动画(Android3.0)
  • VectorDrawable(Android5.0)(后期会单独更新博客讲解)

2. 原理

动画实际上就是在指定的时间段内持续地修改某个属性的值,使得该值在在指定的范围之内平滑的过渡。

动画40ms.png

看图可知:动画就是在某个时间点根据一定的计算方式计算出属性的取值,并且设置给目标对象。在动画的执行周期内持续执行这个过程,形成动画的效果。

3. 帧动画(Frame动画)

  • 帧动画是一系列图片按照一定的顺序展示的过程。它的原理是在一定的时间段内切换多张有细微差异的图片从而达到动画的效果。

  • 帧动画可以在xml中定义,也可以编码实现。如果再xml文件中定义的话,可以放置在res下的anim或drawable目录中,文件名可以作为资源id在代码中引用,如果完全编码实现,需要使用到AnimationDarawable对象。

  • 在xml中定义帧动画时,<animation-list>元素必须为根元素,它可以包含一或多个<item>元素。

  • android:onshot如果定义为true时,此动画只会执行一次,如果为false则一直循环。

  • <item>元素代表一帧动画,android:drawable指定此帧动画所对应的图片资源,android:duration代表此帧持续的时间,单位为毫秒。

3.1 栗子:首先在XML中定义名为test_drawable.xml的文件,这里就不再讲解在代码中定义帧动画了,推荐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/cancel"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/hiss"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/night"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/ok"
            android:duration="1000"></item>
    
    </animation-list>
  • 然后把动画设置给某个View:例如:将该动画设置为某个ImageView的背景:
    <ImageView
        android:layout_centerInParent="true"
        android:id="@+id/iv_anim"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@drawable/test_drawable"
        />
  • 然后还需要通过Java代码启动该动画:
    ((AnimationDrawable) mImageView.getBackground()).start();

4. 补间动画(tween动画或者View动画)

  • 补间动画就是操作某个控件让其展示出旋转、渐变、移动、缩放的一种转换过程,这成为补间动画。

  • 可以在XML中定义,也可以编码实现,后者就不讲解了,推荐XML定义。

  • 在XML中定义并放置于/res/anim目录下,文件名可以作为资源的id被引用;如果编码实现,需要使用到Animation对象或者AnimationSet。

  • Animation对象有四个:AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation四种动画方式,这里只讲解AnimationSet动画集合。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:anim/cycle_interpolator"
     android:shareInterpolator="true">

    <alpha
        android:duration="5000"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"></alpha>
    <scale
        android:duration="5000"
        android:fromXScale="0.2"
        android:fromYScale="0.2"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.5"
        android:toYScale="1.5"></scale>
    <translate
        android:duration="5000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="0"
        android:toYDelta="500"></translate>
    <rotate
        android:duration="5000"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:startOffset="0"
        android:toDegrees="360">
    </rotate>

</set>
    AnimationSet animationSet = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.test_tween);
    mImageView.startAnimation(animationSet);

说明:

  1. <set>是一个动画容器,管理多个动画的数组,与之相对应的Java对象是AnimationSet。

  2. 它有两个属性:android:interpolator代表的是一个插值器的资源,可以引用系统自带的插值器资源,也可以自定义插值器资源,默认值死活匀速插值器。android:shareInterpolator代表<set>里面的多个动画是否要共享插值器,默认值是true,即共享插值器,如果是false,那么<set>插值器就不再起作用,我们要在每个动画中加入插值器。

插值器.png
  1. alpha:透明度 淡入淡出效果 fromAlpha:开始透明度 toAlpha结尾透明度 浮点值。

  2. scale:缩放 调整空间尺寸 fromXScale:起始的X方向上相对自身的缩放比例:比如1.0代表无变化 0.5代表起始时缩小一倍 2.0代表放大一倍。toXScale fromYScale toYScale 不再讲解 pivotX代表缩放的中轴点X坐标,pivotY代表缩放的中轴点Y坐标。 如果想以中轴点为图像的中心,可以把两个属性值定义为0.5或者50%。 浮点值 。

  3. translate:移动 水平、垂直的位移 fromXDelta代表起始X方向的位置 fromYDelta代表起始Y方向的位置 toXDelta代表结尾X方向的位置 toYDelta代表结尾Y方向的位置。 3种表示方式:浮点数(相对自身原始位置的像素值)、num%(代表相对于自己的百分比)、num%p(代表相对于父类控件的百分比) 以num%为例:toXDelta定义为100% 就表示在X方向上移动自己的1倍距离。

  4. rotate: 旋转动画 fromDegrees起始角度 toDegrees结尾的角度 pivotX 旋转中心的X坐标 pivotY旋转中心的Y坐标 坐标有3种表示方式:数字方式代表相对于自身左边缘的像素值,num%方式代表相对于自身左边缘或顶边缘的百分比,num%p代表相对于父容器的左边缘或顶边缘的百分比。

注意:

  1. 补间动画只能运用在View上,功能较为局限:比如旋转只能在x、y轴,不能在z轴。
  2. 另外补间动画的View的位置,系统认定始终在起始位置。

5. 属性动画

  • 补间动画的最大缺陷就是动画改变的只是显示,但View位置并没有发生变化,View移动后不能响应点击事件。

  • 属性动画解决了补间动画的缺陷,其经常使用的两个类是ObjectAnimator和AnimatorSet。

  • 属性动画通过调用属性get、set方法真实地控制一个View的属性值。

5.1 ObjectAnimator

  • ObjectAnimator是属性动画最重要的类:创建一个ObjectAnimator只需通过静态工厂类直接返还一个ObjectAnimator对象。

  • 参数包括一个对象和对象的属性名字,但这个属性必须有get和set方法,其内部会通过Java发射机制调用set方法修改对象的属性值。

    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mImageView,"translationX",200);
    objectAnimator.setDuration(3000);
    objectAnimator.start();

说明:通过ObjectAnimator的静态方法,创建一个ObjectAnimator对象,查看ObjectAnimator.java的静态方法ofFloat():

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

说明:第一个参数是Object的目标View;第二个参数是要操作的属性;最后一个参数是一个可变的float类型的数组,需要传进去该属性变化的取值过程,这里设置了一个参数,变化到200.

  • 属性动画也可以设置市场、插值器等属性。下面是一些常用的属性动画的属性值:
translationX 和 translationY : 用来沿着X轴或者Y轴进行平移。
rotation、rotationX、rotationY:用来围绕View的支点进行旋转。
PrivotX 和 PrivotY :控制VIew对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认该支点位置是View对象的中心点。
alpha :透明度,默认是1(不透明),0代表完全透明
x和y: 描述View对象在其容器中的最终位置。
  • 需要注意ObjectAnimator使用时,要操作的属性必须有set和get方法,不然失效。当然可以自定义一个属性类或者包装类来间接地给这个属性增加get和set方法:
    private static class MyView {
    
        private View mTarget;

        private MyView(View mTarget) {
            this.mTarget = mTarget;
        }

        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
    MyView myView = new MyView(mImageView);
    ObjectAnimator.ofInt(myView, "width", 5000).setDuration(5000).start();
  • ObjectAnimator 动画的监听
objectAnimator.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) {

            }
        });

说明:大部分的时候我们只关心onAnimationEnd事件,Android也提供了AnimatorListenerAdapter来让我们选择必要的事件进行监听。

 objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });

5.2 ValueAnimator

  • ValueAnimator是在一定的时间段内不断地修改对象的某个属性值。

  • 我们只需要将属性的取值范围、运行时长提供给ValueAnimator,那么它就会自动帮我们计算属性值在各个动画运行时段的取值,这些值会按照一定的计算方式来实现平滑过渡。

  • 此外:ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。

  • ValueAnimator 不仅功能强大,API设计也非常简单:通过ofFloat、ofInt等静态工厂函数构建ValueAnimator:例:下面就是我们将数值从1.0 过渡到0.0的动画:

    //ValueAnimator
    private void startValueAnimation() {
        final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
        animator.setDuration(5000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Float newValue = (Float) valueAnimator.getAnimatedValue();
                Log.e("","startValueAnimation newValue :"+newValue);
                mImageView.setAlpha(newValue);
            }
        });
        animator.start();
    }

说明:

  1. ValueAnimator不提供任何动画效果,它更像一个数值发生器,用来产生一定规律的数字,从而让调用者控制动画的实现过程。
  2. 通常:在ValueAnimator的AnimatorUpdateListener中监听数值的变化,从而完成动画的变化。

强调:

  1. 启动动画后,每次更新属性值时就会调用AnimatorUpdate函数,在这里获取新的属性值。
  2. 这里并没有将这个值运动到具体的对象上,它只操作属性值本身,这个值不属于某个具体的对象,所以它的优势在于可以运用到任意的对象上。

5.3 AnimatorSet

  • 先看代码:
    ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, 200.0f, 0f);
    ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageView,"scaleX",1.0f,2.0f);
    ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageView,"rotationX",0.0F,90.0F,0.0F);

    AnimatorSet set = new AnimatorSet();
    set.setDuration(1000);
    set.play(animator1).with(animator2).after(animator3);

说明:

  1. 首先创建了3个ObjectAnimator,分别是animator1 animator2 animator3,然后创建了AnimatorSet。
  2. 先执行animator3,然后同时执行animator1和animator2。
  3. 也可以调用set.playTogether(animator1,animator2)来使者两种动画同时执行。
  4. AnimatorSet类提供了一个play()方法,我们将Animator对象(ValueAnimator或ObjectAnimator),将会返回一个AnimatorSet.Builder实例,我们看看play方法的源码:
    public Builder play(Animator anim) {
        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
    return null;
    }
  1. 我们可以看到play方法中创建了一个AnimatorSet.Builder类,这个Builder类是AnimatorSet的内部类,我们看看Builder类中有什么:
public class Builder {

        private Node mCurrentNode;

        Builder(Animator anim) {
            mCurrentNode = mNodeMap.get(anim);
            if (mCurrentNode == null) {
                mCurrentNode = new Node(anim);
                mNodeMap.put(anim, mCurrentNode);
                mNodes.add(mCurrentNode);
            }
        }

        public Builder with(Animator anim) {
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
            node.addDependency(dependency);
            return this;
        }

        public Builder before(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
            node.addDependency(dependency);
            return this;
        }

        public Builder after(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(node, Dependency.AFTER);
            mCurrentNode.addDependency(dependency);
            return this;
        }

        public Builder after(long delay) {
            // setup dummy ValueAnimator just to run the clock
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(delay);
            after(anim);
            return this;
        }

    }
  1. 从Builder源码中我们可以看出来它采用了建造者模式,每次调用方法时都返回Builder自身用于继续构建。AnimatorSet.Builder中包括以下4个方法:

after(Animator anim):将现有的动画插入到传入的动画之后执行。

after(long delay):将现有动画延迟指定毫秒之后执行。

before(Animator anim):将现有动画插入到传入的动画之前执行。

with(Animator anim):将现有的动画和传入的动画同时执行。

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

推荐阅读更多精彩内容