教你一步一步实现图标无缝变形切换


今天周五,提前祝大家周末快乐。


虽说是最后一天,但是今天这篇来自 huachao1001 投稿的代码量可以抵得上之前四天的文章了。文章开头直接贴出了效果图,感兴趣的朋友可以深入读一读,每一步都写得很详细(同时我想说我也花了“详细”的时间来编辑)。


huachao1001 的博客地址:http://blog.csdn.net/huachao1001


前言


在上一篇文章《酷炫的Activity切换动画,打造更好的用户体验 》(这里指作者博文的上一篇,想查看的可访问开头介绍的博客地址。)中,我们感受到了过渡切换动画带来的不一样的用户体验。如何你还意犹未尽,那么今天我们再体验一把图标切换动画。之前看过Material Design的图标切换,如下图:




感觉效果挺好的,但是发现很多实现是通过多个图片切换产生的动画效果。如果想要定制属于自己的切换效果显然得要去制作很多张图片,导致apk变大不说,这得需要一定的flash功底啊~,于是我就想是否可以通过属性动画,根据起始path数据和最终的path数据产生动画效果。先来个我们的最终效果图,让你更有动力往下看(PS:以下gif是放慢了的动画,另外gif丢帧导致不流畅,各位不要觉得很卡哈~):


path变形:




旋转切换:




加减变形:




在API 21后,系统内置了 AnimatedVectorDrawable ,它能将两个Path以动画方式切换。可是,毕竟不兼容5.0之前的版本,这个类还是过几年再用吧~。既然不用 AnimatedVectorDrawable 类,我们就自己写一个呗~。


读取SVG path并显示


SVG绘制路径的命令虽然不多,如下:


参考 W3School中SVG path教程:

http://www.w3school.com.cn/svg/svg_path.asp


  • M : 相当于 moveTo 两个参数表示移动终点位置的x,y

  • L :相当于 lineTo 两个参数表示x ,y

  • H :相当于水平的 lineTo,需要一个参数表示 lineTo 的x坐标,y坐标则是当前绘制点的坐标

  • V :相当于垂直的 lineTo 需要一个参数表示 lineTo 的y坐标

  • C :curveto(相当于cubicTo,需要6个参数,分别表示第1、2控制点坐标以及结束点的坐标

  • S :4个参数,表示平滑的使用3阶贝塞尔曲线,另一个控制点坐标被省略,需要我们去计算

  • Q :二阶贝塞尔曲线,4个参数,分别表示控制点和结束点坐标

  • T :平滑使用二阶贝塞尔曲线,只有2个参数表示结束点,控制点需要我们计算

  • A :绘制弧线,参数比较复杂,有7个参数

  • Z  :相当于 close path,无参数


其中 S、T、A 几个命令较复杂,本文先不去实现这几个命令,感兴趣的童鞋可以自己去实现。首先,一个Path是由多个Path组成,由于需要实现动画效果,也就是Path里面的数据我们需要动态变化,我们把各个Path“片段”封装到一个对象中。一个“片段”对应一个 svg path 的命令,因为参数最多是3个点(Point),我们只需封装3个Point对象:


class FragmentPath {
   //记录当前path片段的命令    public PathType pathType;
   // 数据占用长度,同样是Line to,    // V、H与L后面携带的数据长度不同,    // 这里需要记录    public int dataLen;
   public Point p1;
   public Point p2;
   public Point p3; }


其中,PathType 是枚举类型,枚举类型无需加 V、H 命令,因为 V、H 在最终绘制的时候还是要转为 LineTodataLen 参数用于记录当前的命令所占的字符串长度。PathType 枚举类型如下:


enum PathType {
   MOVE, LINE_TO, CURVE_TO, QUAD_TO,  CLOSE
}


SVG path 的操作太多,我们把这些操作单独封装到一个 SVGUtil 中,并将其设置为单例模式:




由于 SVG path 中的数据可能写的格式不同,比如使用M命令,有些人会写成:M 100 100而有些人会写成M 100,100这还算好的了,因为看起来比较“规矩”,以空格或逗号分隔字符串就可以提取数据。有些人可能会写成M100,100,也就是在命令字母两边没有加空格,这就让你没办法提取数据了。另外还有就是用户不小心多加了几个空格,或者多加了几个逗号,这让你读取也会带来很多麻烦。还有就是用户还可能把M写成小写的m,在SVG中大小写的含义是不同的,但是我们不是去实现标准的SVG显示,我们可以去忽略大小写,我们只是借鉴一下SVG的命令,顺带学习一下SVG而已。说了那么多,就是为了引入一个话题:需要对用户原始数据进行预处理,在 SVGUtil 类中添加如下函数:



对原始数据做了预处理后,开始真正的将数据转换为Path对象了,在SVGUtil类中添加如下函数:




我们看到,参数中有宽高的放缩倍数。为什么需要放缩倍数呢?我们知道,SVG是矢量图,放缩后图片清晰度是无影响的,因此我们这里需要加放缩倍数。另外我们注意到还有个 nextFrag 函数,用于提取下一条命令,并封装为 FragmentPath 对象,在 SVGUtil 类中添加如下函数:




接下来就是自定义 View了,由于接下来我们需要实现动画效果,因此我们就将自定义的View继承 SurfaceView



最后,再看看我们的布局文件以及自定义的布局属性:


styles.xml添加如下:


<declare-styleable name="SVGPathView">
    <attr name="svg_start_path" format="reference" />
    <attr name="svg_end_path" format="reference" />
    <attr name="svg_paint_width" format="integer" />
    <attr name="svg_view_width" format="integer" />
    <attr name="svg_view_height" format="integer" />
    <attr name="svg_color" format="color" />
</
declare-styleable>


activity_main.xml:




布局文件中可以看到,我们设定的path里面的数据,参考的宽高是100,看看我们的path是怎么写的:




最终会有一个箭头显示处理,无论我们的SVGPathView宽高如何,都会等比放缩。先看看最后显示的图吧:




两个Path以动画方式变形


为了避免每次都通过解析字符串的方式来生成 Path对象,我们需要把 ArrayList<String> 转为 ArrayList<FragmentPath> 即保存已经解析过的命令,减少重复解析。修改 SVGPathView 类中的 svgStartDataList和svgEndDataList


// 动画起始Path数据
private ArrayList<FragmentPath> svgStartDataList;
// 动画结束时的Path数据
private ArrayList<FragmentPath> svgEndDataList;


并在构造函数中,修改 svgStartDataList和svgEndDataList 对象创建方式:




SVGUtil 中添加 strListToFragList 函数:




SVGPathView 类中的 drawPath 函数也需要修改,因为我们是通过属性动画动态生成Path了,而不是当初直接解析原始数据生成Path,将 drawPath 修改如下:


public void drawPath(Path path) {
    clearCanvas();
    mPaint.setStyle(Style.STROKE);
    mPaint.setColor(mPaintColor);

    mCanvas.drawPath(path, mPaint);
   Canvas canvas = surfaceHolder.lockCanvas();    canvas.drawBitmap(mBitmap, 0, 0, mPaint);    surfaceHolder.unlockCanvasAndPost(canvas); }


SVGPathView 类中新加一个函数 startTransform,用于开启动画,作为开始执行的入口函数:




可以看到,真正的核心函数是SVGUtilparseFragList函数,这个函数是根据起始的Path数据和终止的Path数据,以及动画变化时刻的数据,生成新的Path,这个函数也不复杂:




好啦,看看动画吧:




我们再加上旋转动画一起执行,让切换效果更自然一点,先设置rotateDegree属性,并在 onAnimationUpdate 函数中添加 rotateDegree = animatorFactor * 360 ;注意,需要在drawPath函数执行之前添加。


将drawPath中的

mCanvas.drawPath(path, mPaint);

改为:

mCanvas.save(); 
mCanvas.rotate(rotateDegree, mWidth / 2, mHeight / 2);
mCanvas.drawPath(path, mPaint);


效果如下:




动画设置时间为1秒,加上Gif丢帧的原因,所以上面效果看起似乎有点不流畅。


最后,请注意,两个变形的Path数据中,对应的命令格式一定要一模一样,否则会出错!!!!


比如,要实现如下效果:




path数据则必须写成:




虽然减号可以通过如下就可以画出来:


<string name="svg_remove">M 10,50 H 90 </string>


但是,我们需要加号中后半段数据的最终变形位置,因此不可以省去后面的。


源码地址可点击最后 阅读原文 查看。




如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值