Android使用SVG矢量创建很酷的动态效率!
尊重原创,欢迎转载。转载请注明: FROM 
 GA_studio   http://blog.csdn.net/tianjian4592
一个真正酷炫的动效往往让人虎躯一震,话不多说。咱们先瞅瞅效果:
这个效果我们须要考虑下面几个问题:
1. 这是图片还是文字;
2. 假设是图片该怎样拿到图形的边沿线坐标,假设是文字呢?
3. 假设拿到了边沿线坐标,怎样让光线沿着路径跑动;
4. 怎么处理过程的衔接;
以上四个问题似乎不是太优点理,而这几个问题也正好是这个效果精华所在,接下来咱们一个一个进行考虑,当然这样的考虑已经基于一些国外大神的基础之上。
首先这是图片还是文字?
答案是:背景是图片。表面的文字还是图片。有些同学可能会说了,靠,这么没含量。一个帧动画而已。还虎躯一震,XXXXX,当然,答案肯定不会是这种,背景我就不说了,普通的jpg或png图,但文字则是SVG格式的矢量图。
有了第一个问题的答案。我们来看第二个问题,怎样拿到文字图形的边沿坐标。
要回答这个问题。我们先来简单的了解一个SVG(矢量图);
SVG 意为可缩放矢量图形(Scalable Vector Graphics),是使用 XML 来描写叙述二维图形和画图程序的语言;
使用 SVG 的优势在于:
1.SVG 可被许多的工具读取和改动(比方记事本),因为使用xml格式定义。所以能够直接被当作文本文件打开,看里面的数据;
2.SVG 与 JPEG 和 GIF 图像比起来。尺寸更小,且可压缩性更强,SVG 图就相当于保存了重要的作用点,比方要显示一个圆,须要知道圆心和半径。那么SVG 就仅仅保存圆心坐标和半径数据,而寻常我们用的位图都是以像素点的形式依据图片大小保存相应个数的像素点,因而SVG尺寸更小。
3.SVG 是可伸缩的。寻常使用的位图拉伸会发虚。压缩会变形。而SVG格式图片保存数据进行运算展示,无论多大多少,能够不失真显示;
4.SVG 图像可在不论什么的分辨率下被高质量地打印;
5.SVG 可在图像质量不下降的情况下被放大;
6.SVG 图像中的文本是可选的。同一时候也是可搜索的(非常适合制作地图);
7.SVG 能够与 Java 技术一起执行;
8.SVG 是开放的标准;
9.SVG 文件是纯粹的 XML;
看起来好厉害的样子,还是回到我们的问题,从SVG图中我们可否拿到我们想要的数据点呢?依据上面的介绍,答案当然是肯定的。从SVG图中我们能够拿到我们想要的全部数据;
好的,拿到数据之后,怎么让一条线沿着路径跑起来呢?毋庸置疑。我们须要用到path;
最后我们依据效果的须要,设置几个绘制过程,进行绘制;
接下来我们一起来解决以上问题:
既然SVG是公认的xml文件格式定义的,那么我们则能够通过解析xml文件拿到相应SVG图的全部数据。我们先看下 path 类型的SVG 数据:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg"> <path d="M250 150 L150 350 L350 350 Z" /> </svg>
上面有一个path 标签,里面用到了 M 和 Z 指令,M 就相当于 android Path 里的moveTo(),Z 则相当于 Path 里的close();
我们先看下SVG 里关于path 有哪些指令:
M = moveto 相当于 android Path 里的moveTo(),用于移动起始点
L = lineto 相当于 android Path 里的lineTo(),用于画线
H = horizontal lineto 用于画水平线
V = vertical lineto 用于画竖直线
C = curveto 相当于cubicTo(),三次贝塞尔曲线
S = smooth curveto 相同三次贝塞尔曲线。更平滑
Q = quadratic Belzier curve quadTo(),二次贝塞尔曲线
T = smooth quadratic Belzier curveto 相同二次贝塞尔曲线。更平滑
A = elliptical Arc 相当于arcTo(),用于画弧
Z = closepath 相当于closeTo(),关闭path
了解了以上path相关的指令,就能够看懂path构成的SVG图的数据了。除此之外,SVG里还定义了一些主要的图形和效果:
很多其它介绍和使用大家能够看 W3School
好,以上内容。我们已经知道 SVG 图是通过 Xml 格式定义的。而且里面用到了一些主要的指令对数据进行组装,构成基本图形或复杂的路径。
而对于我们来说 ,这个xml 怎样拿到呢?
1.我们依据最后要做的效果。利用PS等作图软件设计制作出想要的图形;
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGlhbmppYW40NTky/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
2. 使用 GIMP 之类的矢量图软件导出图片的SVG数据,方法例如以下:
先使用魔棒工具高速建立选区:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGlhbmppYW40NTky/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
然后将选区导出为path:
这个时候在软件的右边栏就能够看见生成的路径了。然后将路径导出:
经过以上几步。我们就拿到了我们自己设计的文字或图形SVG图的Path数据。上面图片的SVG信息例如以下:
<? xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="http://www.w3.org/2000/svg"
width="6.95746in" height="1.82269in"
viewBox="0 0 668 175">
<path id="Selection"
fill="none" stroke="black" stroke-width="1"
d="M 530.00,34.00
C 530.00,34.00 526.08,59.00 526.08,59.00
526.08,59.00 518.00,105.00 518.00,105.00
518.00,105.00 515.42,119.00 515.42,119.00
515.42,119.00 513.26,125.01 513.26,125.01
513.26,125.01 506.00,126.00 506.00,126.00
506.00,126.00 496.00,126.00 496.00,126.00
496.00,126.00 496.00,120.00 496.00,120.00
490.87,124.16 486.71,126.42 480.00,126.91
475.71,127.22 471.06,126.94 467.00,125.44
454.13,120.68 451.86,110.19 452.00,98.00
452.22,79.34 465.14,64.55 484.00,63.18
492.14,62.59 498.96,65.71 504.00,72.00
504.00,72.00 510.00,34.00 510.00,34.00
510.00,34.00 530.00,34.00 530.00,34.00 Z
M 551.00,56.89
C 539.01,55.86 537.45,39.82 551.00,35.55
568.60,33.45 567.67,58.33 551.00,56.89 Z
中间段省略
M 263.00,134.00
C 263.00,134.00 263.00,145.00 263.00,145.00
263.00,145.00 202.00,145.00 202.00,145.00
202.00,145.00 202.00,134.00 202.00,134.00
202.00,134.00 263.00,134.00 263.00,134.00 Z" />
</svg>
依据图形路径的复杂度,生成的path数据复杂度也不一样,但格式也算是很的清楚。即採用一定的指令把数据点进行拼接;
如今有了这些数据点,我们须要做的则是对数据进行解析,封装成我们要的Path;
解析的过程也无非是 遇到指令则採用android Path 里的相应方法进行置换。解析方式例如以下:
public Path parsePath(String s) throws ParseException {
        mCurrentPoint.set(Float.NaN, Float.NaN);
        mPathString = s;
        mIndex = 0;
        mLength = mPathString.length();
        PointF tempPoint1 = new PointF();
        PointF tempPoint2 = new PointF();
        PointF tempPoint3 = new PointF();
        Path p = new Path();
        p.setFillType(Path.FillType.WINDING);
        boolean firstMove = true;
        while (mIndex < mLength) {
            char command = consumeCommand();
            boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);
            switch (command) {
                case 'M':
                case 'm': {
                    // m指令,相当于android 里的 moveTo()
                    boolean firstPoint = true;
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1,
                                relative && mCurrentPoint.x != Float.NaN);
                        if (firstPoint) {
                            p.moveTo(tempPoint1.x, tempPoint1.y);
                            firstPoint = false;
                            if (firstMove) {
                                mCurrentPoint.set(tempPoint1);
                                firstMove = false;
                            }
                        } else {
                            p.lineTo(tempPoint1.x, tempPoint1.y);
                        }
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }
                case 'C':
                case 'c': {
                    // c指令,相当于android 里的 cubicTo()
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1, relative);
                        consumeAndTransformPoint(tempPoint2, relative);
                        consumeAndTransformPoint(tempPoint3, relative);
                        p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y,
                                tempPoint3.x, tempPoint3.y);
                    }
                    mCurrentPoint.set(tempPoint3);
                    break;
                }
                case 'L':
                case 'l': {
                    // 相当于lineTo()进行画直线
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1, relative);
                        p.lineTo(tempPoint1.x, tempPoint1.y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }
                case 'H':
                case 'h': {
                    // 画水平直线
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        float x = transformX(consumeValue());
                        if (relative) {
                            x += mCurrentPoint.x;
                        }
                        p.lineTo(x, mCurrentPoint.y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }
                case 'V':
                case 'v': {
                    // 画竖直直线
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        float y = transformY(consumeValue());
                        if (relative) {
                            y += mCurrentPoint.y;
                        }
                        p.lineTo(mCurrentPoint.x, y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }
                case 'Z':
                case 'z': {
                    // 封闭path
                    p.close();
                    break;
                }
            }
        }
        return p;
    }
有了图形相应的path,我们仅仅须要依照我们想要的效果进行绘制就可以,详细过程不再细讲。大家看代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == STATE_NOT_STARTED || mGlyphData == null) {
return;
} long t = System.currentTimeMillis() - mStartTime; // 绘制出现前的边沿线和跑动过程
for (int i = 0; i < mGlyphData.length; i++) {
float phase = MathUtil.constrain(0, 1,
(t - (mTraceTime - mTraceTimePerGlyph) * i * 1f / mGlyphData.length)
* 1f / mTraceTimePerGlyph);
float distance = INTERPOLATOR.getInterpolation(phase) * mGlyphData[i].length;
mGlyphData[i].paint.setColor(mTraceResidueColors[i]);
mGlyphData[i].paint.setPathEffect(new DashPathEffect(
new float[] {
distance, mGlyphData[i].length
}, 0));
canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint); mGlyphData[i].paint.setColor(mTraceColors[i]);
mGlyphData[i].paint.setPathEffect(new DashPathEffect(
new float[] {
0, distance, phase > 0 ? mMarkerLength : 0,
mGlyphData[i].length
}, 0));
canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint);
} if (t > mFillStart) {
if (mState < STATE_FILL_STARTED) {
changeState(STATE_FILL_STARTED);
} // 绘制渐变出现的过程。即改变alpha过程
float phase = MathUtil.constrain(0, 1, (t - mFillStart) * 1f / mFillTime);
for (int i = 0; i < mGlyphData.length; i++) {
GlyphData glyphData = mGlyphData[i];
mFillPaint.setARGB((int) (phase * ((float) mFillAlphas[i] / (float) 255) * 255),
mFillReds[i],
mFillGreens[i],
mFillBlues[i]);
canvas.drawPath(glyphData.path, mFillPaint);
}
} if (t < mFillStart + mFillTime) {
ViewCompat.postInvalidateOnAnimation(this);
} else {
changeState(STATE_FINISHED);
}
}
好了,基本的问题和思路基本如上,有些人可能会说。你这讲的跟UX分享似的,没毛线用。事实上我的目的仅仅有一个。那就是无论你能否看懂代码。都能依照我上面所说做出自己想要的效果。并加以改变,灵活运用。毕竟轮子不须要反复造!
我本人也是对SVG矢量图刚有所了解,主要參考国外大神的一篇博客。链接例如以下:http://www.willowtreeapps.com/blog/muzei-esque-animated-svg-drawing-for-android/
CSDN源代码下载地址:http://download.csdn.net/detail/tianjian4592/8548495
版权声明:本文博客原创文章,博客,未经同意,不得转载。
Android使用SVG矢量创建很酷的动态效率!的更多相关文章
- Android使用SVG矢量动画(二)
		上篇我们学习了怎么显示SVG矢量图像,当然还有一个更强大的功能,就是让SVG图像动起来,先上一张效果图吧: 要实现上述动画效果,就得用AnimatedVectorDrawable这个类了,它就是负责V ... 
- Android 通过Java代码生成创建界面。动态生成View,动态设置View属性。addRules详解
		废话不多说,本文将会层层深入给大家讲解如何动态的生成一个完整的界面. 本文内容: Java代码中动态生成View Java代码中动态设置View的位置,以及其他的属性 LayoutParams详解 一 ... 
- Android 使用 SVG 矢量图
		android svg矢量图 可能包含的操作有: SVG图还包括改变颜色,透明度,大小,矩阵操作(平移.旋转.缩放),selector, (图标,背景,按钮),动画,等 setTint(int Col ... 
- Android SVG矢量资源的使用方法
		VectorDrawable 与 SVG Android 5.0(Lollipop, API 21)后,新增了<vector>标签,以VectorDrawable的形式支持SVG类型矢量图 ... 
- Android中使用SVG矢量图(一)
		SVG矢量图介绍 首先要解释下什么是矢量图像,什么是位图图像? 1.矢量图像:SVG (Scalable Vector Graphics, 可伸缩矢量图形) 是W3C 推出的一种开放标准的文本式矢量图 ... 
- Android使用SVG小结
		SVG的全称是Scalable Vector Graphics,叫可缩放矢量图形.它和位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质量下降.它的优点在于节约空间,使用方便. andr ... 
- 自己制作 Android Vector Asset 矢量图
		版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/c5138891/article/deta ... 
- Android编程示例:创建机场计划模拟器应用程序
		在本文中,我们将演示如何使用Android Studio和Java编程语言创建一个示例Android应用程序,从“临时”实现高级响应用户界面的功能.本文中讨论的应用程序将实现机场航班时刻表模拟的功能. ... 
- Android 开发 VectorDrawable 矢量图 (三)矢量图动画
		VectorDrawable 矢量图 三部曲: Android 开发 VectorDrawable 矢量图 (一)了解Android矢量图与获取矢量图 Android 开发 VectorDrawabl ... 
随机推荐
- hdu1532 (最大流入门,EK算法)
			看着这个博客 然后敲了hdu1532这个入门题,算是对最大流有点理解了 #include <stdio.h> #include <string.h> #include < ... 
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block
			原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block 到目前为止,我们使用的模块都是在同一个配置 ... 
- sgu 286. Ancient decoration(最小环覆盖)
			给你一个n个点,每个点度为k(k为偶数)的无向图,问是否能将图中的n条边染色,使得每个点都拥有两条被染色的边.也就是说,是否存在拥有原图中n条边的子图,使得每个点的度为2?仔细想想,每个点的度为2,实 ... 
- 基于Cocos2dx开发卡牌游戏_松开,这三个国家
			1.它实现了动态读取地图资源.地图信息被记录excel桌格.假设你想添加地图,编者excel导入后CocoStudio数据编辑器,然后出口到Json档,到项目的Resource文件夹下. 2.SGFi ... 
- ubuntu 安装输入法(fcitx)
			目前搜狗输入法是基于fcitx框架下的,所以我们得安装fcitx才行 首要得卸载Ubuntu默认的ibus输入法:sudo apt-get remove ibus 然后添加fcitx的nightlyP ... 
- [原创] linux 下上传 datapoint数据到yeelink 【golang版本】同时上传2个数据点
			/* Create by sndnvaps<sndnvaps@gmail.com> * data: 2015-04-12* upload 2 datapoint to yeelink.ne ... 
- MySql语句大全:创建、授权、查询、修改等(转)
			林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 一.用户创建.权限.删除 1.连接MySql操作 连接:mysql -h 主机地址 -u 用户 ... 
- CenOS下安装jdk
			1. 安装JDK1.7.0 下载完成后在取得root权限后执行: [root@sea sea]# sudo rpm -ivh /目录/jdk-7-linux-x64.rpm 执行结果: Prepari ... 
- filestream.read(buffer,offset,count)的正确解释
			filestream.read(buffer,offset,count) offset是buffer的偏移量 所以,filestream.read(buffer,1,count)会报下面的错 Syst ... 
- WMI 获取硬件信息的封装函数与获取联想台式机的出厂编号方法
			原文:WMI 获取硬件信息的封装函数与获取联想台式机的出厂编号方法 今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都是可以提取出来的,就自己把那些公共部分提出出来,以后如果要获取 某部分的 ... 
