1.SVG是什么?
svg 是Scalable Vector Graphics的缩写,指可伸缩矢量图形,可以用于绘制复杂不规则的控件。
svg绘制原理,就是利用了Path绘制图形。
1)svg利用xml定义图形。在xml中就包晗了绘制Path所需的数据。
2)加载xml中的PathData,转换成Path对象。
3)利用Canvas,把Path绘制在屏幕上了。
4)处理点击事件。
path支持的指令有:
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():关闭路径
2.利用SVG绘制中国地图
1)利用Dom解析xml数据,把省份信息解析成java对象。
下面就是svg的数据格式。其中pathData保存的就是绘制省份地图所需要的path信息。
<path
id="340000"
title="安徽"
class="land"
pathData="M541.02,336.29L541.71,336.09L543.77,338.27L543.53,338.58..."
利用Dom解析Xml。将xml中定义的标签转换成JavaBean
关键代码将xml中定义的PathData转换成Java中Path对象。
Path path = PathParser.createPathFromPathData(pathData);
InputStream in = getResources().openRawResource(R.raw.china);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = null;
try {
documentBuilder = factory.newDocumentBuilder();
Document document = documentBuilder.parse(in);
NodeList nodeList = document.getElementsByTagName("path");
list = new ArrayList<>();
float left = -1;
float top = -1;
float right = -1;
float bottom = -1;
for (int i = 0; i < nodeList.getLength(); i++) {
Element node = (Element) nodeList.item(i);
//拿到定义在xml中的pathData数据
String pathData = node.getAttribute("pathData");
//利用PathParser转换成Path对象。
Path path = PathParser.createPathFromPathData(pathData);
String name = node.getAttribute("title");
ProviceItem item = new ProviceItem();
item.path = path;
item.name = name;
item.drawColor = colorArray[i % colorArray.length];
list.add(item);
RectF rectF = new RectF();
path.computeBounds(rectF, true);
//下面是为了拿到整个地图的最左、最上、最右、最下的坐标值。
left = left == -1 ? rectF.left : Math.min(left, rectF.left);
top = top == -1 ? rectF.top : Math.min(top, rectF.top);
right = right == -1 ? rectF.right : Math.max(right, rectF.right);
bottom = bottom == -1 ? rectF.bottom : Math.max(bottom, rectF.bottom);
}
//通过坐标值,构建一个矩形,就是地图要绘制的矩形区域。
mapRectF = new RectF(left, top, right, bottom);
} catch (Exception e) {
e.printStackTrace();
}
将得到的list集合设置给自定义的MapView。
public void setData(List<ProviceItem> list, RectF rectF) {
this.list = list;
this.mapRectF = rectF;
if (mapRectF != null) {
//拿到地图的原始大小
double mapWidth = mapRectF.width();
//拿到缩放比,使绘制的地图宽度铺满。
scale = (float) (mWidth / mapWidth);
}
//调用View的onDraw方法
postInvalidate();
}
在onMeasure时,拿到MapView的宽度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
}
2)自定义View绘制地图。执行完MapView的onDraw方法,地图就显示到了屏幕上。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (list != null && list.size() > 0) {
canvas.save();
//按获得的缩放比,对画布进行缩放
canvas.scale(scale, scale);
for (ProviceItem proviceItem : list) {
//selectItem 是被选中的item。
//调用ProviceItem的方法,绘制自己省份的地图。
proviceItem.drawItem(canvas, paint, selectItem == proviceItem);
}
}
}
将JavaBean中封装的Path对象,通过canvas.drawPath(path,paint),绘制到屏幕上。
public void drawItem(Canvas canvas, Paint paint, boolean isSelect) {
//选中状态
if (isSelect) {
//清除绘制的阴影
paint.clearShadowLayer();
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
paint.setShadowLayer(1, 2, 2, 0xffffff);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
//将拿到的path绘制到画布上
canvas.drawPath(path, paint);
} else {
//未选择状态
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(drawColor);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
//将拿到的path绘制到画布上
canvas.drawPath(path, paint);
}
}
3)给每一个省份添加点击事件。首先要判断点击的是哪个省份地图。
在ViewGroup做事件分发处理时,判断触摸点落在哪个View上,View是一个矩形,比较容易判断,省份是一个不规则的图形,怎么判断呢?
先看下ViewGroup中是如何处理点击事件的落点的。
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
但是要判断触摸点,落在哪个地图上,和上面的类似,但也有不同。
通常View可以接收事件的区域是一个规则的矩形,但地图确实不规则的,
这里用到了Region,来对不规则的View做事件的相应。具体做法,看代码实现。
复写onTouchEvent方法。
在按下时进行判断,也可以在手指滑动过程中进行判断。
手指抬起后,恢复原来状态。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下时响应
handleOnTouch((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_MOVE:
//移动过程中响应
handleOnTouch((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_UP:
//手指抬起来后,恢复原来状态
selectItem = null;
postInvalidate();
break;
}
return true;
}
拿到手指落点的x,y坐标和省份的Region进行对比。Region封装了一个path。
调用region.contains((int)x, (int)y)方法,就可以和落点进行比对。
ProviceItem selectItem = null;
private void handleOnTouch(int x, int y) {
if (list == null || list.size() <= 0) {
return;
}
ProviceItem select = null;
for (int i = 0; i < list.size(); i++) {
ProviceItem item = list.get(i);
//isTouch返回true,说明一件匹配到了相应的省份地图来响应事件。
if (item.isTouch(x / scale, y / scale)) {
select = item;
if (select != selectItem) {//判断按下的是同一个,则不重复触发绘制。
//调用onDraw方法。
postInvalidate();
}
selectItem = select;
return;
}
}
}
public boolean isTouch(float x, float y) {
RectF rectF = new RectF();
//计算出path路径的边距,并把得到的值存在RectF中
path.computeBounds(rectF, true);
//创建一个和RectF矩形大小的区域
Region region = new Region();
//将path的路径,设置到region中,
region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
//通过这个api就能够判断触摸点,是否落在了path绘制的区域内
return region.contains((int)x, (int)y);
}
到这,中国地图绘制、事件的相应核心代码已经完毕。
举一反三,上面的代码也可以用于绘制其他SVG等不规则的图形。
核心点就三个,解析xml,绘制path,事件响应。
Demo链接:
android开发之SVG实现中国地图-Android文档类资源-CSDN下载