svg path绘制心形_Android 绘制中国地图

本文介绍了如何在Android应用中使用SVG Path数据来绘制中国地图和心形,包括SVG的基本概念、Path指令解析,并提供了将SVG Path转换为Android Path的工具类,以及在地图上高亮特定区域的实现思路。
摘要由CSDN通过智能技术生成

bff17f986a671402239f98208ad15ccc.png

作者:最后的温存,地址:http://lastwarmth.win/2019/01/25/china-map/


最近的版本有这样一个需求:

ed3d6f45be102e63986135743e36477e.png

有 3 个要素:

  • 中国地图
  • 高亮省区
  • 中心显示数字

面对这样一个需求,该如何实现呢?

高德地图

因为项目是基于高德地图来做的,所以很自然而然的想到了高德。但是当查阅高德地图相关 Api 后,发现并没有能够实现这样需求的方法,所以只能另寻他法了。

图片叠加

让设计师出图,实现第一个要素开发成本极低。至于高亮省区,也是继续让设计师出图,与全国地图分辨率保持一致,为每个省区设计一张高亮的图,其他地方透明,这样算下来设计师得出 35 张图。若不考虑性能,将图片无脑叠加倒也可以实现。但是作为 Android 开发都知道,这样的一张不算小的图片加载到手机里,占用的内存怕是个庞然大物,更别谈极端情况下要叠加 35 张这样的大图了。优化下叠加方案:将高亮的省区做成小图,一个包含了省区所有区域的矩形,省区内部高亮,其他区域透明,这样图变小了,但是就得计算小图相对于全国大图的相对位置,对于每个小图都得计算一个比例。同时,绘制高亮省区时可以每次都只取2张图进行叠加,叠加完后释放一张图再加载另一张图,而不用一次性全部加载在内存中。这种方案想想是 ok 的,但是感觉依然还是很麻烦。于是继续探索~

SVG Path

其实网上有很多文章也是有类似的需求,简单搜一下就发现了 SVG 这个解决方案了。看了一眼,便决定就是它了!SVG:可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。元素是 SVG 基本形状中最强大的一个,它不仅能创建其他基本形状,还能创建更多其他形状。

SVG Path 用 Android 绘制

这里先贴一下我找的北京市的 Path 数据:


这里要注意一点:SVG Path 里的数据都是在一个固定宽高的矩形里的坐标集合,所以当 Android View 与 SVG 的宽高不一致时,需要进行缩放。 注意下面代码中的 scale 属性:

/**
* 计算地图边界
* 1.黑龙江是中国最东,最北的省份
* 2.新疆是中国最西的省份
* 3.海南是中国最南的省份
*/
private fun computeBounds() {
val hljRF = RectF()
xPaths[HEILONGJIANG_CODE]?.computeBounds(hljRF, true)

val hnRF = RectF()
xPaths[HAINAN_CODE]?.computeBounds(hnRF, true)

mapWidth = hljRF.right
mapHeight = hnRF.bottom
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val speSize = View.MeasureSpec.getSize(widthMeasureSpec)
scale = speSize / mapWidth
setMeasuredDimension(speSize, (speSize * mapHeight / mapWidth).toInt())
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 缩放画布
canvas.scale(scale, scale)
...
}

再来看到 Path 里有一些 M、L、Z 等字符,这些都是 Path 元素里的指令,后面紧跟的数字即是坐标。

  • M x,y 移动指令,映射 Path 中的 moveTo
  • L x,y 画直线指令,映射 Path 中的 lineTo
  • H x 画水平线指令,映射 Path 中的 lineTo,不过要使用上一个坐标的 y
  • V y 画垂直线指令,映射 Path 中的 lineTo,不过要使用上一个坐标的 x
  • C x1,y1,x2,y2,x,y 三次贝塞尔曲线指令,映射 Path 中的 cubicTo
  • S x2,y2,x,y 跟在 C 指令后面使用,用 C 指令的结束点做控制点,映射 cubicTo
  • Q x1,y1,x,y 二次贝塞尔曲线指令,映射 quadTo
  • T x,y 跟在 Q 指令后面使用,使用 Q 的 x,y 做控制点,映射 quadTo
  • Z path 关闭指令,映射 close

注意小写指令为使用相对坐标,下面 2 行 Path 得到的结果是一样的:

M421.139,189.75L420.782,186.894
M421.139,189.75l-0.357,-2.856

基于 Android Path 实现不了小写指令的那种效果,所以只能使用大写指令。这里贴一下一个将 SVG Path 转成 Android Path 的工具类:

/**
* 仅限大写指令转换
*/
public class SvgPathToAndroidPath {
private int svgPathLenght = 0;
private String svgPath = null;
private int mIndex;
private List cmdPositions = new ArrayList<>();/**
* M x,y
* L x,y
* H x
* V y
* C x1,y1,x2,y2,x,y
* Q x1,y1,x,y
* S x2,y2,x,y
* T x,y
* */public Path parser(String svgPath) {this.svgPath = svgPath;
svgPathLenght = svgPath.length();
mIndex = 0;
Path lPath = new Path();
lPath.setFillType(Path.FillType.WINDING);//记录最后一个操作点
PointF lastPoint = new PointF();
findCommand();for (int i = 0; i < cmdPositions.size(); i++) {
Integer index = cmdPositions.get(i);switch (svgPath.charAt(index)) {case 'M': {
String ps[] = findPoints(i);
lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));
lPath.moveTo(lastPoint.x, lastPoint.y);
}break;case 'L': {
String ps[] = findPoints(i);
lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));
lPath.lineTo(lastPoint.x, lastPoint.y);
}break;case 'H': {//基于上个坐标在水平方向上划线,因此y轴不变
String ps[] = findPoints(i);
lastPoint.set(Float.parseFloat(ps[0]), lastPoint.y);
lPath.lineTo(lastPoint.x, lastPoint.y);
}break;case 'V': {//基于上个坐标在水平方向上划线,因此x轴不变
String ps[] = findPoints(i);
lastPoint.set(lastPoint.x, Float.parseFloat(ps[0]));
lPath.lineTo(lastPoint.x, lastPoint.y);
}break;case 'C': {//3次贝塞尔曲线
String ps[] = findPoints(i);
lastPoint.set(Float.parseFloat(ps[4]), Float.parseFloat(ps[5]));
lPath.cubicTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]), Float.parseFloat(ps[4]), Float.parseFloat(ps[5]));
}break;case 'S': {//一般S会跟在C或是S命令后面使用,用前一个点做起始控制点
String ps[] = findPoints(i);
lPath.cubicTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));
lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));
}break;case 'Q': {//二次贝塞尔曲线
String ps[] = findPoints(i);
lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));
lPath.quadTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));
}break;case 'T': {//T命令会跟在Q后面使用,用Q的结束点做起始点
String ps[] = findPoints(i);
lPath.quadTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));
lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));
}break;break;case 'Z': {//结束
lPath.close();
}break;
}
}return lPath;
}private String[] findPoints(int cmdIndexInPosition) {int cmdIndex = cmdPositions.get(cmdIndexInPosition);
String pointString = svgPath.substring(cmdIndex + 1, cmdPositions.get(cmdIndexInPosition + 1));return pointString.split(",");
}private void findCommand() {
cmdPositions.clear();while (mIndex < svgPathLenght) {char c = svgPath.charAt(mIndex);if ('A' <= c && c <= 'Z') {
cmdPositions.add(mIndex);
}
++mIndex;
}
}
}

实现

利用工具类获取每个省区的 Android Path,全部绘制一遍,即可绘制出全国地图(优化:高亮的省区这一步不绘制,避免绘制两次)。

针对高亮省区,调整画笔颜色再绘制一遍即可。

显示数量:这个目前没想到什么好方法,只能让设计师参照地图宽高比标出每个中心点的位置,就像这样:

2225e4673b7ebb649cb68af1e2dbb726.png

然后手动算出每个点的横纵坐标占比,再进行绘制。绘制数量计算坐标时仍要考虑 scale 属性。

参考
  • Android 上绘制中国省份地图
  • SVG 转 Android Canvas Path

---END---

推荐阅读:
远程上班第一天,钉钉、企业微信集体“崩溃”
Android 10 适配指南,有这几点需要注意!
【译】Android 玩转Path动画
【译】你的Android库是否还在Application中初始化?
ViewPager2有哪些新花样?一文带你深入了解ViewPager2
a5a00ab15b5a66639557ebe6479ec47f.png 每一个“在看”,我都当成真的喜欢7b133ecb20a6abe23028d6b825cd53ef.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值