目录php
1、前言java
2、SVG小课堂android
3、简单使用git
4、实战github
5、写在最后canvas
SVG 在安卓5.0被引入,由于其放大后不会模糊的优秀表现,被使用也是愈来愈多。今天小盆友也来谈谈这个优秀的SVG,同时分享一些我的比较喜欢的知识小点。老规矩,先上实战图。数组
"手写"掘金 微信
SVG 全称 Scalable Vector Graphics ,翻译一下即为 可缩放的矢量图形。ide
SVG 的优势不少,并且在不一样的场景优势也会有所不一样,小盆友以为 SVG 给我带来的优势以下几点svg
这个缺点,说的并非SVG的缺点,而是在 Android 中使用SVG的缺点或局限。
前言中提到 SVG 是在5.0以后引入,虽然做为一个图标资源并不会有兼容问题。
可是若是对 SVG 进行使用动画时,则须要进行兼容性处理。否在 5.0 如下会闪退,毕竟 4.4 的占有率还 10.3%左右(以下图,图片来自 Android Studio 的统计)。
至于如何使用和兼容,咱们在下一小节进行说明。
动画限制这一点其实准确来讲,不属于缺点,小盆友认为是不够灵活。
由于SVG的动画是经过属性动画进行执行的,咱们知道属性动画最终是反射调用到类的 setXxx(Xxx就是咱们设置的属性名称),因此若是该类没有对应的方法则是没有做用的。
对 “属性动画” 源码兴趣的童鞋能够移步小盆友的另外一篇博文,带有活力的属性动画源码分析与实战。
接下来的一个问题就是,属性动画反射回调的类是哪一个类呢?这里有两种状况,一种是针对 Group 标签,一种是针对 Path 标签。但在说明具体具体类以前,咱们有必要说明 Group 和 Path 标签的层级关系。
以下图所示,叶子节点只能为Path标签,而 Group标签用于装载Path标签或Group标签。值得一提的是 Vector 能够直接包含一个或多个Path, 而不必定须要包含Group。
Path标签对应的是 VectorDrawableCompat$VFullPath,而 VectorDrawableCompat$VFullPath 继承于 VectorDrawableCompat$VPath,这两个类的内部方法以下,一样用红框圈出 set 开头的方法,因此咱们经过属性动画对Path标签进行控制的只能这几个属性。
咱们先来阐述如何将SVG常规使用起来。但在这以前咱们须要说明一下 SVG 中绘制 Path 的语法。
path 的 pathData属性内装载的就是路径数据,其语法以下
M = moveto(M X,Y) :将画笔移动到指定的坐标位置
L = lineto(L X,Y) :画直线到指定的坐标位置
H = horizontal lineto(H X):画水平线到指定的X坐标位置 V = vertical lineto(V Y):画垂直线到指定的Y坐标位置 C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三阶贝赛曲线
S = smooth curveto(S X2,Y2,ENDX,ENDY):三阶贝赛曲线 Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二阶贝赛曲线 T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射 A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线 Z = closepath():关闭路径
复制代码
小盆友我的认为,这些语法做为一个了解便可,并不须要记忆,由于 SVG 的资源文件通常不须要咱们程序猿自行绘制,只是偶尔须要修改一下,因此要求并非很高。
如今有不少在线编辑SVG工具,能够经过绘制后,将路径数据拷贝下来稍做修改,即可使用。
“手写”掘金 的 SVG资源就是小盆友从掘金官网获取后,进行一些简单的修改,因此只须要了解,须要修改时会运用就行。
在 Android 中的常使用的模版为
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="xxdp" android:height="yydp" android:viewportWidth="xx" android:viewportHeight="yy">
<group>
<path android:fillColor="#006CFF" android:pathData="xxxx" />
....more path or group
</group>
....more path or group
</vector>
复制代码
在 vector 标签中的 android:width
和 android:height
表示的是 SVG的大小,而 android:viewportWidth
和 android:viewportHeight
表示的是将 android:width
和 android:height
划分红多少个等份,随后的 Group 和 Path 的坐标则是基于这一比例进行编写。
group 和 path 咱们在前面已经提过了,就再也不赘述。
咱们举个简单的例子,用 SVG画出 以下图形,并将其使用
// ic_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:name="top"
android:pathData="
M 20,20
L 50,20 80,20"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineCap="round" />
<path
android:name="middle"
android:pathData="
M 20,50
L 50,50 80,50"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineCap="round" />
<path
android:name="bottom"
android:pathData="
M 20,80
L 50,80 80,80"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineCap="round" />
</vector>
复制代码
使用其实和普通的图片资源同样,ic_menu资源 即是咱们的 SVG 图形
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="10dp"
android:src="@drawable/ic_menu" />
复制代码
这里不存在兼容问题,小盆友在4.4的机子上也有测试过。
SVG 的动画是比较有趣的,但咱们在 “动画限制问题” 小节中提到,存在着兼容问题,5.0以前的版本不能使用SVG动画。
因此咱们须要新建一个 drawable-anydpi-v21
文件夹,来存放咱们的动画资源,具体存放结构和代码以下
animated-vector
起着
扣接 SVG静态资源 和 属性动画 的做用。
// menu.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_menu">
<target
android:name="top"
android:animation="@animator/top_anim" />
<target
android:name="bottom"
android:animation="@animator/bottom_anim" />
</animated-vector>
// top_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="pathData"
android:valueFrom="
M 20,20
L 50,20 80,20"
android:valueTo="
M 20,50
L 50,20 50,20"
android:valueType="pathType" />
// bottom_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="pathData"
android:valueFrom="
M 20,80
L 50,80 80,80"
android:valueTo="
M 20,50
L 50,80 50,80"
android:valueType="pathType" />
复制代码
值得一提的是,这里的 pathData 最终就是调用了 VectorDrawableCompat$VPath 中的 setPathData,而参数类型便为 pathType。忘记的童鞋能够回 “动画限制问题” 小节查看下。
若是只是把咱们这里使用的 menu资源放在 drawable-anydpi-v21
文件夹下,运行于 4.4的机子时,会报找不到相应资源的错误。因此咱们须要在 drawable
文件夹下,建一个相同名字的资源 menu资源,只是里面的内容不是 animated-vector
做为根标签,而是使用和 ic_menu资源 彻底同样的内容。
最终在代码中进行兼容处理 5.0以后的版本开启动画,以前的版本切换图片资源
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
((Animatable) img1.getDrawable()).start();
} else {
img1.setImageDrawable(
ContextCompat.getDrawable(SvgUseActivity.this, R.drawable.ic_back));
}
复制代码
5.0以后版本的效果以下。5.0以前版本就只是简单图片切换,就不上图了:
上一小节咱们知道,对 SVG 添加动画,简单方便,可是也说明了使用系统自带的这一套操做没法实现较为复杂的交互,因此咱们只能本身动手,才能丰衣足食了。
还记得小盆友在介绍优势时,说到SVG的格式是XML,这就是咱们本身动手的切入点。由于格式为XML,因此能够自行解析,拿取其中的pathData数据转为Path路径,接下来就能够作不少有趣的事情。咱们融入到实战中来体会这一趣事。
效果图
编码思路
(1)解析 SVG 文件 首先须要将 “掘金”这一SVG进行XML解析,咱们借助 DocumentBuilderFactory
类,为咱们解析获取一棵DOM树。
// 从 XML文档 生成 DOM对象树
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document = null;
try {
document = factory.newDocumentBuilder().parse(inputStream);
} catch (SAXException |
IOException |
ParserConfigurationException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
(2)获取并保存Path的数据 在上一步中获取到DOM树以后,进行遍历DOM节点获取到 Path 数据,保存其填充的颜色和将 pathData 的数据翻译成 Path对象进行保存起来。
这里须要借助 PathParser
类将 pathData
的数据翻译成 Path对象 ,可是PathParser类 被打上了注解 @hide,咱们没法直接使用,因此只能是将其拷贝一份放置咱们的目录下来使用。具体核心代码以下
// 遍历全部的 Path 节点
for (int i = 0; i < pathNodeList.getLength(); ++i) {
Element pathNode = (Element) pathNodeList.item(i);
// path 的 svg 路径
String pathData = pathNode.getAttribute(PATH_DATA);
// path 的 颜色
String colorData = pathNode.getAttribute(FILL_COLOR);
// 解析 path
Path path = null;
try {
path = PathParser.createPathFromPathData(pathData);
} catch (Exception e) {
e.printStackTrace();
}
// path 解析出错,退出
if (path == null) {
mHandle.sendEmptyMessage(InnerHandler.ERROR);
return;
}
int color = Color.parseColor(colorData);
path.computeBounds(rect, true);
left = left == -1 ? rect.left : Math.min(left, rect.left);
right = right == -1 ? rect.right : Math.max(right, rect.right);
top = top == -1 ? rect.top : Math.min(top, rect.top);
bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
PathData item = new PathData();
item.path = path;
item.color = color;
pathDataList.add(item);
}
复制代码
(3)进行缩放 根据 SVG图像大小 和 画布大小,进行偏移和缩放,让SVG图像大小合适且居中显示于画布中。核心代码以下
float mScale = calculateScale(mSvgRect.width(), mSvgRect.height(), getWidth(), getHeight());
// 移至中心
mCanvasMatrix.preTranslate(getWidth() / 2, getHeight() / 2);
mCanvasMatrix.preTranslate(-mSvgRect.width() / 2, -mSvgRect.height() / 2);
mCanvasMatrix.preScale(
mScale,
mScale,
mSvgRect.width() / 2,
mSvgRect.height() / 2);
canvas.setMatrix(mCanvasMatrix);
复制代码
(4)借助 PathMeasure 和 属性动画,让其进行勾勒后填充 属性动画开启后,每次刷新都经过 PathMeasure 对当前须要勾勒的Path进行裁剪绘制,达到一步步勾勒的效果。核心代码以下
PathData pathData = mPathDataList.get(index);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(pathData.color);
mPaint.setStrokeWidth(mLineWidth / mScale);
mPathMeasure.setPath(pathData.path, false);
mPathMeasure.getSegment(0,
mPathMeasure.getLength() * process,
mAnimPath,
true);
canvas.drawPath(mAnimPath, mPaint);
复制代码
PathMeasure的使用,能够查看小盆友的另外一篇博文:PathMeasure的API讲解与实战
效果图
Github入口:传送门
编码思路
(1)解析SVG数据 与“手写”掘金的事例同样,第一步也是解析数据,经过 PathParser
类将svg的数据转为Path对象,而颜色填充则由咱们设置的数组决定。
同时还要保存好svg图像的大小,具体核心代码以下:
// 用于记录整个 svg 的实际大小
float left = -1;
float top = -1;
float right = -1;
float bottom = -1;
// 计算出 path 的 rect
RectF rect = new RectF();
// 遍历全部的 Path 节点
for (int i = 0; i < pathNodeList.getLength(); ++i) {
Element pathNode = (Element) pathNodeList.item(i);
// path 的 svg 路径
String pathData = pathNode.getAttribute(DATA);
// path 的 title
String title = pathNode.getAttribute(TITLE);
// 省略一些代码
path.computeBounds(rect, true);
left = left == -1 ? rect.left : Math.min(left, rect.left);
right = right == -1 ? rect.right : Math.max(right, rect.right);
top = top == -1 ? rect.top : Math.min(top, rect.top);
bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
ItemData itemData = new ItemData(path,
ContextCompat.getColor(getContext(), mMapColor[i % colorSize]),
title);
mapDataList.add(itemData);
}
mSvgRect.left = left;
mSvgRect.top = top;
mSvgRect.right = right;
mSvgRect.bottom = bottom;
复制代码
(2)缩放地图至View中心 根据画布的大小 和 svg的大小,将咱们的画布进行偏移和缩放,使咱们的地图大小合适且居中放置(这里借助了矩阵,但最终会将该矩阵做用于咱们的画布)
// 移至画布中心
mCanvasMatrix.preTranslate(getWidth() / 2, getHeight() / 2);
// 移外边
float lastLeftMargin = mLastRectF.left - mSvgRect.left;
float lastTopMargin = mLastRectF.top - mSvgRect.top;
mCanvasMatrix.preTranslate(-lastLeftMargin, -lastTopMargin);
// 移至中心
mCanvasMatrix.preTranslate(-mLastRectF.width() / 2, -mLastRectF.height() / 2);
// 进行缩放
if (!mLastRectF.isEmpty()) {
mScale = calculateScale(
mLastRectF.width(),
mLastRectF.height(),
getWidth(),
getHeight());
}
mCanvasMatrix.preScale(
mScale,
mScale,
lastLeftMargin + mLastRectF.width() / 2,
lastTopMargin + mLastRectF.height() / 2);
复制代码
(3)如何交互 至此咱们的地图就已经能正常显示了,但还须要交互。交互最主要的问题是咱们如何知道选中的是哪块区域。具体经过一下代码进行判断,即可知道咱们是否触碰了 该Path所包含的区域
/** * 是否在触碰的范围内 * * @param item 地图的每一个数据项 * @param x 触碰点的x轴 * @param y 触碰点的y轴 * @return true:在范围内;false:在范围外 */
private boolean isTouch(ItemData item, float x, float y) {
item.path.computeBounds(mTouchRectF, true);
mTouchRegion.setPath(
item.path,
new Region((int) mTouchRectF.left,
(int) mTouchRectF.top,
(int) mTouchRectF.right,
(int) mTouchRectF.bottom)
);
return mTouchRegion.contains((int) x, (int) y);
}
复制代码
(4)剩余操做 得到了点击的区域,如何进行动画的过渡就是计算逻辑问题了。小盆友这里就再也不展开讲这块的逻辑。这里用一句话归纳,就是经过比较 上一次选中的Path区域 和 此次选中的Path区域 进行 中心坐标偏移和缩放。
SVG 也是一把利器,挥舞得当可让本身的App展示出别人所想不到的交互效果,但愿这篇文章能让你体会到不同的SVG。若是你有所收获就给我一个赞❤️并关注我吧,若是发现有那些欠妥的地方,请留言区与我讨论,咱们共同进步。
高级UI系列的Github地址:请进入传送门,若是喜欢的话给我一个star吧😄
欢迎加我微信,咱们能够进行更多更有趣的交流