Android高级动画(2)

目录

Android高级动画(1)http://www.jianshu.com/p/48554844a2db
Android高级动画(2)http://www.jianshu.com/p/89cfd9042b1e
Android高级动画(3)http://www.jianshu.com/p/d6cc8d218900
Android高级动画(4)http://www.jianshu.com/p/91f8363c3a8c

来点硬货

前面一篇文章已经讲了Android中大部分的动画框架,回顾一下有:Tween动画,属性动画,帧动画,CircularReveal,Activity转场动画,5.0新转场动画,Interpolator插值器,5.0转场动画分为Explode、Slide、Fade、Share四种模式。合理且充分利用这些动画,我们已经可以做出很多优美的效果了。

但是今天这篇文章我们来讲讲大名鼎鼎的矢量动画,它颠覆了前面所有的动画。前面的动画都是对控件做动画,而矢量动画是对图形做动画,矢量动画可以做出前面任何一个动画框架都做不到的效果。好了,NB就先不吹了,开始我们的学习吧。

从矢量图形说起

我们平时看到的图片大多数都是位图,英文名叫 bitmap,位图对应的格式就是 .bmp,看过bmp的人都知道,体积那叫一个大啊。。。一张小小的Logo都能2M,于是jpg,png这些压缩格式就出现了,优秀的压缩算法极大地减少了图片体积。配合索引位图、灰度图等手段,图片可以压缩的非常小,世界一下子变得美好~

但是,开心没多久,问题又来了。不管你的压缩算法有所优秀,位图有2个天生的缺点无法避免:
(1)图片放大会失真
(2)图片尺寸越大,体积越大

不管是做Android开发还是IOS开发,我们都需要适配不同分辨率的手机,也就意味着同一个ImageView在不同手机上的图片分辨率是不同的,如果我们只用一套图片,那必然存在放大失真问题。统一的解决方案就是为每一种分辨屏幕准备一套图片。这样失真的问题解决了,但是图片体积又大了。

似乎两者是不可兼得的,怎么办呢?

Android高级动画(2)_第1张图片
靓仔

矢量图登场

矢量图不同于位图是用像素描述图像的,它是用数学曲线描述图形。所以一张图片就是对应着一系列的数学曲线,所以图片的显示尺寸和图片体积无关。(这里为什么说显示尺寸,因为矢量图根本就没有所谓的尺寸,就看你把它显示成多大),它的体积就是文本文件的大小。并且矢量图可以无限拉伸不失真。

先来看一个Android中使用矢量图的例子:

Android高级动画(2)_第2张图片
爱心

哇,这个爱心有点漂亮~

代码实现:




    

PS:这个文件非常小,只有670字节,连1KB都不到。而且我们只需要这一个文件,就可以适配所有分辨率,无限拉伸不失真。)

根节点是vector,width和height属性是显示大小,但是实际上这个大小是可以根据控件改变的。viewportHeight和viewportWidth也是宽高,它是定义曲线函数时所参照的宽高。子节点path就是定义绘制内容的,fillColor是填充颜色,pathData是描绘路径。那么问题来了,pathData中的这一串字母数字是什么东东?

SVG

说到这,SVG必须得登场了。SVG就是标准的矢量图格式,Android中使用矢量图虽然没有直接使用SVG图片,但是基本格式是和SVG一样的。

SVG语法
SVG的语法太复杂了,这里不可能全部讲一遍。为了说明问题,我们就讲几个最基础的命令。
M:新建起点,参数x,y(M20, 30)
L:连接直线,参数x,y(L30, 20)
H:纵坐标不变,横向连线,参数x(H20)
V:横坐标不变,纵向连线,参数y(V30)
Q:二次贝塞尔曲线,参数x1,y1,x2,y2(Q10,20,30,40)
C:三次贝塞尔曲线,参数x1,y1,x2,y2,x3,y3(C10,20,30,40,50, 60)
Z:连接首尾,闭合曲线,无参数

掌握以上这些基本命令之后,我们基本上就可以画出90%的图形了。比如上面demo只用到了三个命令:M、C、Z,我们整个系列所有demo用到的命令也就只有M、L、C、Z。

(至于什么是二次贝塞尔,什么是三次贝塞尔,如果不了解的话请自行百度,不能再拓展了,否则这篇文章要突破万字了。)

让矢量图形动起来

虽然我们已经可以绘制漂亮的矢量图形了,但是我们这个系列是Android高级动画啊,得动起来,Android中怎样才能让矢量图形动起来呢?

animated-vector登场

animated-vector从名字上看就是动起来的vector,先看示例:

Android高级动画(2)_第3张图片
animated-vector

初始显示的是三条横线,然后从三条横线的状态变化到箭头,同时整体旋转360度。

代码如下:
(1)首先是layout文件,一个普通的ImageView,src指向一个drawable


(2)drawable根节点是一个animated-vector,drawable参数用于指定初始显示的样子,下面两个target子节点用于指定动画,第一个target是指定了旋转动画,第二个target指定了path转变动画。下面我们分别来看下初始的drawable和两个target。



    

    

(3)初始drawable,这个根节点vector就是定义了宽高,第三层path节点就是初始显示的矢量图形,它有填充色和path路径。




    
        
    

但是外面又包了一层group,这个是干什么用的呢?

animated-vector规定,可以有多个动画同时进行,但是一个对象上只能加载一个动画。上面的例子可以看到三条线图形转变成箭头图形,同时旋转360度,那就要有两个动画,一个做path变换,一个做旋转。但是两个动画不能同时放在一个对象上,所以必须用group包一层,把path变换动画放在path对象上,把旋转动画放在group对象上,从而实现整体的效果。

(4)target1,这就是一个简单的属性动画,旋转360度



(5)target2,这个动画是最神奇的地方,它用于从一个path变换到另一个path,valueFrom指定变换前的path,valueTo指定变换后的path,propertyName和valueType在这个例子中是固定写法。



    

这里需要重点提下valueFrom和valueTo

valueFrom和valueTo分别指定了变换前的path和变换后的path,它要求前后两个path必须是同形path
PS:如果两个path拥有相同的命令数,并且对应位置的命令符相同,那么这两个path我们称之为同形path。
如:
M10,15 L20,20 L25,15 C10,20,20,20,30,10 L50,50 Z
M20,30 L10,10 L15,25 C25,10,30,30,10,20 L35,35 Z

(6)java代码启动动画

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void startAnim(View view) {
    Drawable drawable = imgBtn.getDrawable();
    ((Animatable) drawable).start();
}

至此,一个简单的animated-vector就完成了,估计有人要吐槽了。
“我的天,一个动画要写这么多代码?”
是的,这一点木有办法,矢量动画本身就比较复杂。但是别伤心,因为更复杂的还在后面呢。。。

矢量选择器

animated-vector已经很强大了是吧,但是肯定有人发现问题了,animated-vector只能从一个path变换到另一个path,不能反向再变回来。如果我需要在两个path之间来回变换该怎么办呢?

Android高级动画(2)_第4张图片
靓仔2

animated-selector登场。

selector我们大家都很熟悉了,用于一个按钮的点击效果。animated-selector类似,也是用于两个状态的切换,只不过animated-selector是在两个path之间来回切换显示。

先看演示:

Android高级动画(2)_第5张图片
PathMorphing

是不是很酷炫!迫不及待地想知道是怎么实现的。

(1)首先是Layout,一个普通的 ImageView,src指向一个 drawable


(2)再看drawable,drawbale是一个animated-selector,子节点是两个item和两个transition。




    

    

    

    

两个item分别指定了两种状态下要显示的样子,两个transition分别指定了当状态切换时所做的动画。

具体来说:第一个item指定的是state_on时显示的样子,第二个item指定的是state_off时显示的样子。第一个transition指定的是从off切换到on时所做的动画,第二个transition指定的是从on切换到off时所做的动画。

下面来分别看下两个item和两个transition

(3)两个item




    

        
    




    

        
    

根节点是一个vector,里面是一个group包着一个path,和前面说的一样,这个group不是必须的,往往是为了加载动画而增加的。path是定义路径。

PS:这里有人可能会有疑问,这些“爱心”、“Twitter”的path是怎么生成的呢?这里先提前简单地解释下:对于简单的图形,我们可以手动计算,比如上面三条横线变成箭头的例子,就是手动计算点坐标的。对于复杂的图形,比如Twitter和爱心,手动计算不现实,我们可以找一些辅助软件来生成。)

(4)两个transition




    
        
            
        
    

    
        
            
        
    




    
        
            
        
    

    
        
            
        
    

两个transition分别定义了从 off 到 on 和从 on 到 off 时切换动画。

根节点是一个animated-vector,它有一个重要的属性drawable,这个属性指定动画前的初始状态,就是一个普通的vector。

下面两个target是定义动画,每一个target都有一个name属性,指定动画作用于哪个对象上,这个对象就是上面drawable里定义的名字。这一层就是固定写法,我也不知道为什么要定义这一层~[/尴尬]。再下面就是ObjectAnmator,定义具体的动画。通过propertyName来区分动画类型,rotation是旋转,pathData是path转换。旋转动画就不说了,path动画转换前面也分析过了。

OK,至此我们已经把动画都定义好了。因为比较复杂,我们再来捋一遍。
(1)首先定义一个animated-selector,它定义两个item,对应两种状态on、off的显示,再定义两个transition用于状态变化时启动动画。
(2)两个item是vector类型,定义要显示的path。
(3)两个transition是animated-vector类型,定义从一个状态到另一个状态时的动画,在指定动画时要注意,一个对象上只能加载一个动画,如果动画个数比对象个数多,要用group把对象包裹起来。

可是问题来了,这样只是定义好了动画,但是还是动不起来啊。因为animated-selector怎么知道View的状态变化了呢?所以还差最后一步,把View的状态和animated-selector关联起来。

private boolean isTwitterChecked = false;
public void onTwitterClick(View view) {
        isTwitterChecked = !isTwitterChecked;
        final int[] stateSet = {android.R.attr.state_checked * (isTwitterChecked ? 1 : -1)};
        imageView.setImageState(stateSet, true);
    }

好啦,这样当我们点击图片时,通过调用imageView.setImageState就可以切换状态,从而切换 Twitter 和 heart 的显示。再来欣赏下动画吧。

Android高级动画(2)_第6张图片
PathMorphing

trimPath

trimPath也是一种动画类型,它是通过对路径的裁剪实现的动画。先看示例:

Android高级动画(2)_第7张图片
TrimPath

效果还是比较酷炫的,代码实现和上面Twitter基本类似。直接贴代码:
(1)animated-selector基本和上面类似,就不分析了




    

    

    

    

(2)两个item也基本类似,也不分析了




    

    




    

    

(3)两个transition,这里和前面稍有不同。我们拿最后一个分析下。




    
        
            
        
    

    
        
            
        
    




    
        
            
        
    

    
        
            
        
    

首先根节点是一个animated-vector,有一个drawable属性,下面包含两个target子节点,整体结构上没有变化。区别就在target中的ObjectAnimator。

propertyName是trimPathStart,表示这是一个trimPath类型的动画(还有trimPathEnd,方向相反)。trimPath的原理是从一段path上裁剪出一小部分显示,通过改变裁剪的长度,形成一个渐变的动画。

上面的demo中,其实是有两段path,一段是放大镜,一段是横线。就像这样:

Android高级动画(2)_第8张图片
image

初始状态,横线显示的长度是0,所以我们只能看到一个放大镜:

Android高级动画(2)_第9张图片
image

动画开始后,放大镜裁剪的部分逐渐变小,横线裁剪的部分逐渐变大,直至放大镜消失,只剩横线。

Android高级动画(2)_第10张图片
image

trimPath动画的写法也基本是固定的


valueFrom和valueTo表示裁剪的起始点和结束点,valueType是float类型,duration是1000毫秒。这样就实现了放大镜和横线切换显示的动画啦。

总结

这一篇我们基本讲完了Android中的矢量动画,这块知识点都不难,就是太乱。我尽量把思路捋的顺一点了,用问题引出问题的方式把所有知识点串起来,这样更容易理清关系。如果完整看到这里的话你一定会发现还是有问题,Android系统提供的vector、animated-vector、animated-selector虽然很强大,但是有一个致命的缺点,就是只能在xml中写死,不能通过java代码动态构建,并且我们不能控制动画的过程。所以这又是个头疼的问题。怎么办呢,下一个靓仔在哪里?

下一篇

下一篇应该是这个系列总结篇,我们会在系统矢量动画的基础上封装一些自己的库,实现一些额外的功能。最后我们还会封装一个通用动画库,简化动画的使用。

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