android 动画系列 (4) - PropertyAnimation 属性动画

这是我这个系列的目录,有兴趣的可以看下: android 动画系列 - 目录

啊,终于来到了属性动画这里了,属性动画是3.0之后推出的,目的就是用来替换 tween 动画, tween 动画的缺点实在让人不能忍啊。

  • 首先就是拓展性太差,只能写移动、缩放、旋转、渐变四种动画,以及这四种动画的组合,不支持自定义View的拓展。
  • 其次一个致命的缺点就是动画只是屏幕绘制上的动画,控件的属性并没有改变,一个经典的问题就是一个Button从一个地方移动到另一个地方,点击事件还是在原来的地方。

所以Tween动画我们现在也基本放弃了,所有使用Tween动画的场景都可以用属性动画来替代。

属性动画呢,就灵活多了,属性动画顾名思义就是使用动画的方式来操作对象的属性,这样就不必限制在tween 动画的4种效果,可以玩更多的了,比如说颜色动画,path 动画等等很多的玩法。

在本质上讲属性动画就是使用数值发生器根据变化曲线和时间生成一系列数值,作用在你需要的对象的属性上形成动画,注意看这里,属性动画的本质不再是强调动画本身了,而是变成了数值发生器,不管你想干什么,属性动画只关心生成数值数列给你去使用,这就是属性动画拓展性的根本,只关心数值的生成。


废话不多说我们直接来看属性动画的使用,首先属性动画的核心类有3个:

  • ValueAnimator
  • ObjectAnimator
  • AnimatorSet

tween 动画学完后这个AnimatorSet大家应该猜到是什么了吧,就是属性动画集合呗,把一堆动画放在一起执行,属性动画的结合提供了几种很灵活的动画执行方式,具体的后面讲。

属性动画的核心:ValueAnimator 数值发生器,他就是上面说的生成数值序列的类。

ObjectAnimator是ValueAnimator的子类,是对ValueAnimator操作的封装,使用起来更简便,一般都是用来对某一个对象的属性做动画的。

下面我们从难易程度来看这3个类


ObjectAnimator

上面说了ObjectAnimator是ValueAnimator的子类,是对ValueAnimator操作的封装,目的就是让我们的属性动画操作更加简单,这符合 java 简单化的思想,大伙仔细想想,简单好用就是我们码代码的终极追求之一啊,下面来看看用法。

和 tween 动画一样,还是以位移 translate 举例

ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", 200);
animator.setDuration(600);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(1);
animator.start();

这就是一个位移动画,向右移动200px,效果图:

ezgif.com-video-to-gif.gif
ObjectAnimator.ofFloat(Object target, String propertyName, float... values)

这就是 ObjectAnimator 的构造方法,有3个参数,targe 是动画作用的 view,propertyName 是我们操作的属性名称,上面我们操作的就是 view 的 translationX x 轴位移属性,大伙要是忘了属性名称,直接点一下看看就知道了

Snip20170730_23.png

最后面的 values 可变参数是属性值,传值上可以传单个也可以传多个值,这里就要说一下了:

  • 传单个值:表示使用 view 当前的属性值作为起始,传入的值作为结速值
  • 传2个值:表示前一个是属性开始值,后一个是属性结束值
  • 传2个以上值:表示第一个是属性开始值,最后一个值是属性结束值,中间的数是动画变化的中间值

传单个值上面我们已经看过了,那么来看看剩下的2种传值方式:

 ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", -200,200);

传2个值效果图:


ezgif.com-video-to-gif.gif

那么来看看传2个以上值是个什么样子滴

 ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", 0,200,500,200);

传2个以上值效果图:


ezgif.com-video-to-gif.gif

看过图大家都应该很清楚的了解了期中的规律,传值这里大家要牢记啊。

缩放,旋转中心点?

哈哈,还记得 tween 动画的旋转和缩放的中心点在哪里吗,那么属性动画呢,我们来试下就知道了

ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "rotation", 0, 360);
animator.setDuration(600);
animator.start();

旋转效果图:


ezgif.com-video-to-gif.gif

ok,测试一个就可以了,默认中心点在 view 的中心,为啥会这样呢,这是跟属性动画的坐标系有关系。tween 动画的坐标以 动画view左上角为原点,坐标系是固定的。属性动画操作的是 view的属性,view 采用视图坐标系,在该坐标系中描述位置的属性有好几套,为了不至于混乱,所以中心点只能放在 view 的中心,换位思考下,要是你来写这个 api,也是只能放在中心了。我最喜欢在学习 google的 api 时思考下作者的思路,挺有意思的哈...


属性动画常用属性

属性动画就是操作view 的属性的,这里列出常用的属性供大家参考,思考下通过这些属性我们可以实现什么效果,有些动画效果就是通过操作我们意想不到的属性来实现的。

常用的propertyName:

  • rotationX 围绕x轴旋转
  • rotationY 围绕y轴旋转
  • rotation 围绕轴点旋转
  • translationX 在x轴方向上平移
  • translationY 在y轴方向上平移
  • scaleX 在x轴方向缩放
  • scaleY 在y轴方向缩放
  • alpha 透明度
  • width 宽度
  • height 高度

视图坐标系

上面在说属性动画,缩放/旋转中心点时说到了一个问题,view 使用视图坐标系,描述 view 坐标的属性有好几种,这里我们一定要分清楚这些属性都表示哪段距离,要不你会发现你再做动画时可能事与愿违。这里科普下 view 的坐标系,摘自: 开发艺术探索

这里我们不会说的很详细,只说属性动画会用到的属性相关的v视图坐标体系。3.0之后,view 添加了4个属性:

  • x : view左上角的坐标,view 到父控件的x 轴距离
  • y : view左上角的坐标,view 到父控件的y 轴距离
  • translationX :view 相对于父控件 x 轴偏移量,默认0
  • translationY :view 相对于父控件 y 轴偏移量,默认0

我设计了一组对比,看图


Snip20170731_30.png

红色的 view 外面套了一层蓝色 view 作为父控件,父控件距离顶部有一段距离,我们打印一下蓝色 view 此时的坐标,包括上面4个属性和 蓝色 view x/y 的绝对坐标

 rootView.post(new Runnable() {
            @Override
            public void run() {
                StringBuffer info = new StringBuffer();
                int[] sizeOnScreen = new int[2];
                viewTest.getLocationOnScreen(sizeOnScreen);
                info.append("screenX=" + sizeOnScreen[0] + "\n");
                info.append("screenY=" + sizeOnScreen[1] + "\n");
                info.append("x=" + viewTest.getX() + "\n");
                info.append("y=" + viewTest.getY() + "\n");
                info.append("translationX=" + viewTest.getTranslationX() + "\n");
                info.append("translationY=" + viewTest.getTranslationY() + "\n");
                info.append("100dp=" + SizeActivity.this.getResources().getDisplayMetrics().density * 100 + "px");
                textInfo.setText(info.toString());
            }
        });

结果图:


Snip20170731_31.png

看结果证明 view 的视图坐标系的确是相对于父控件来说的,view 的上述4个属性一定搞清楚,乱了就麻烦了。


ofFloat/ofInt

大伙注意没有上面的属性动画都是使用 ofFloat 的:『 ObjectAnimator.ofFloat() 』,这是因为 view 的属性大部分都是 float 值,上面列出的常用的属性可都是 float 的,所以做动画也得用 float 的。那么我为啥还要说这个呢,因为有时我们会对颜色做动画,view 的颜色值可是 int 的,要使用 ofInt,你要是用 ofFloat 会直接报类型转换错误的,这点需要注意啊。

ObjectAnimator animator = ObjectAnimator.ofInt(testView, "backgroundColor", Color.parseColor("#FF4081"), Color.parseColor("#3F51B5"));
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(500);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(1);
animator.start();

效果图:


ezgif.com-video-to-gif.gif

ps:可算把上面的那一堆说完了,肯定有人说我啰嗦,但是呢,这都是我曾经有疑问的地方,我想肯定有需要的朋友,所以写的很啰嗦。


ValueAnimator

ValueAnimator 是属性动画的根基了,是应用最广广泛的了,尤其是在自定义 view 中呀。上面使用的ObjectAnimator 都是基于ValueAnimator的。

ValueAnimator也可以叫数值发生器,ValueAnimator的功能就是在两个数值范围内,顺序地产生过渡数值,过渡速率可以通过Intepolator来控制,过渡时间通过duration来设置,最终产生一组有特定变化速率的数值序列。那这个数值序列产生了干嘛呢?你想干嘛就干嘛。

那我们就在 textview 身上玩玩

ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 8888);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
     public void onAnimationUpdate(ValueAnimator valueAnimator) {
     // 当前的数值
     int animatedValue = (int) valueAnimator.getAnimatedValue();
     // 当前动画的进度
     float animatedFraction = valueAnimator.getAnimatedFraction();
     textTest.setText(String.valueOf(animatedValue));
     }
});
valueAnimator.start();

效果图:


ezgif.com-video-to-gif.gif

呵呵,看着挺有意思的,么么哒!ValueAnimator的核心就是[ addUpdateListene ],数据更新接口,每16ms 更新一帧,调用一次这个方法,这个回调方法里我们想干什么都可以,非常自由,这就是属性动画最利于扩展的地方。

本篇是介绍基础部分,ValueAnimator结合自定义 view 部分可以看这个练手项目: AndroidAdvanceAnimation


AnimatorSet

先看代码:

public void togetherRun(View view)  {  
        ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",  1.0f, 2f);  
        ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",  1.0f, 2f);  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.setDuration(2000);  
        animSet.setInterpolator(new LinearInterpolator());  
        //两个动画同时执行  
        animSet.playTogether(anim1, anim2);  
        animSet.start();  
    }  
  
    public void playWithAfter(View view)   {  
        float cx = mBlueBall.getX();  
  
        ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",  1.0f, 2f);  
        ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",  1.0f, 2f);  
        ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall,  "x",  cx ,  0f);  
        ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall,  "x", cx);  
          
        /** 
         * anim1,anim2,anim3同时执行 
         * anim4接着执行 
         */  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim1).with(anim2);  
        animSet.play(anim2).with(anim3);  
        animSet.play(anim4).after(anim3);  
        animSet.setDuration(1000);  
        animSet.start();  
    }  

属性动画集合,前面说过属性动画的集合提供比 tween 动画集合灵活的多的执行方式:

  • animSet.playTogether(anim1... anim5) : 动画一起执行
  • animSet.playSequentially(anim1... anim5) : 按动画的插入顺序,顺序执行

上面2个简单一看就知道了,我得仔细说下上面代码中的 with/after/before 方式,其中 after 和 before的概念很容易混淆

  • animSet.play(anim1).with(anim2) = 1和2同时执行
  • animSet.play(anim4).after(anim3) = 4在3之后执行,换句话说就是3执行完了在执行4
  • animSet.play(anim4).before(anim3) = 4在3之前执行,换句话说就是4执行完了在执行3
  • 最重要一点:animSet.play().with() 是支持这样链式编程的,但是不支持无限链式变成,比如 animSet.play(anim1).with(anim2).before(anim3).before(anim5) 这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行

效果图我就不贴了,今天莫名的好烦,干啥都感觉很不爽!!!


path 动画

属性动画中添加了一个一个意想不到的动画,path 动画,可以让 view 沿着 path 的路径做动画,说实话真是佩服 google 的脑洞,大 google 威武

先来看代码:

 testView.post(new Runnable() {
            @Override
            public void run() {
                // 获取view 的初始位置
                temX = testView.getX();
                temY = testView.getY();
            }
        });

  Path path = new Path();
        path.moveTo(temX, temY);
        path.lineTo(temX + 1000, temY + 1000);

        ObjectAnimator animator = ObjectAnimator.ofFloat(testView, View.X, View.Y, path);
        animator.setDuration(500);
        animator.setRepeatMode(ObjectAnimator.REVERSE);
        animator.setRepeatCount(1);
        animator.start();

效果图:


ezgif.com-video-to-gif.gif

path 大家都知道吧,可以使用 path 绘制复杂的多边形,这里需要注意啊,path 的默认起始点是0,0,你要是不写 moveTo,就是在父控件坐标系0,0的位置开始,这里我获取了 view 的初始坐标。

有4个参数,第一个是目标 view,后面2个是需要操作的 view 的位置坐标,最后是 path,这个方法很简单,没啥难度,主要在于你的脑洞有大多哈。


属性动画监听接口

属性动画动画的三个类 ValueAnimator/ObjectAnimator/AnimatorSet 都直接或是见解继承自 Animator 这个基类,所以添加监听的方式都是一样的

set.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animator) {
         toast("动画开始");
    }
    @Override
    public void onAnimationEnd(Animator animator) {
         toast("动画结束");
    }
    @Override
    public void onAnimationCancel(Animator animator) {
         toast("动画取消");
    }
    @Override
    public void onAnimationRepeat(Animator animator) {
        toast("动画重建");
    }
});

要是嫌麻烦还有一个监听接口的适配器,看 google 起名字多规范,其实我挺羡慕这项技能的...

set.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        toast("动画结束");
    }
});

好了,监听这不多说了,一看就懂的。


属性动画跑在那个线程,帧数?

为啥我会说着这个问题呢,因为在学习过程中,看到过有人说属性动画是100帧/s,这不科学啊,要是能跑到100帧那肯定不在 UI 线程里了,可是没听说啊,我只是看到 系统的SVG动画在7.0之后是跑在单独的线程里的,所以这里特意测试了一下

        count = 0;
        ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                count++;
                float currentAnimatedValue = (float) valueAnimator.getAnimatedValue();
                float animatedFraction = valueAnimator.getAnimatedFraction();
                Log.d("test", "属性动画第:" + count + "次执行," + "线程:" + Thread.currentThread().getName() + ",          currentAnimatedValue:" + currentAnimatedValue + ",animatedFraction:" + animatedFraction);
            }
        });
        animator.setDuration(300).start();

跑了一个 ValueAnimator 动画,300秒,看看会执行多少次,所在线程就明白了,因为属性动画都是继承自 Animator ,跑这一个就可以了

结果图:


Snip20170731_32.png

15ms 刷新一帧,也是跑在UI 线程里的,谣言不攻自破,大伙要是碰到有混乱的地方,不妨自己跑下就明白了。


KeyFrame

KeyFrame 是属性动画中的关键帧的意思,在属性动画的源码中也是有关键帧这个东东的,这个东东的意义在于我们可以决定在时间流速的某个节点,属性值变化率是多少,具体看这里,翻到最下面就是:android动画详解(二)

参考资料

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

推荐阅读更多精彩内容