最近发现可以用SVG来实习非常复杂的自定义View,可以说是非常的厉害了。只要你看到不想做的很复杂的设计图,你就让美工妹子给你来一张SVG吧,还可以趁机和她聊下天。

我们就来画一个可交互的中国台湾地图吧,首先我们来理一下做这种自定义View的步骤。

  1. 下载含有中国地图的 SVG
  2. 用** **网站 将svg资源转换成相应的 Android代码
  3. 利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path
  4. 重写OnDraw方法 利用Path绘制中国地图
  5. 重写OnTouchEvent方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上

对了,地图资源可以在**这里 **下载。开始写代码吧。

项目地址点这里可以看源码。

前两步不需要我如何说了吧,可以把转化后的android代码,放入res/raw目录下,接下来开始第三步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void parseXMLWithPull() {
InputStream inputStream = null;
try {
inputStream = context.getResources().openRawResource(R.raw.taiwanhigh);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(inputStream, "UTF-8");
int eventType = xmlPullParser.getEventType();
ProvinceItem item = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
String nodeName = xmlPullParser.getName();
switch (eventType) {
//开始解析某个节点
case XmlPullParser.START_DOCUMENT:
itemList = new ArrayList<>();
break;
case XmlPullParser.START_TAG:
if ("path".equals(nodeName)) {
String pathData = xmlPullParser.getAttributeValue("http://schemas.android.com/apk/res/android", "pathData");
Path path = PathParser.createPathFromPathData(pathData);
item = new ProvinceItem(path);
}
break;
case XmlPullParser.END_TAG:
if (nodeName.equalsIgnoreCase("path") && item != null) {
assert itemList != null;
itemList.add(item);
item = null;
handler.sendEmptyMessage(PARSE_END);
}
break;
default:
break;
}
eventType = xmlPullParser.next();
}

} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

xml解析写的非常随便,如果有需要修改的请自行修改,这里主要的是将解析出来的path标签下的pathData,然后使用封装好的PathParser(这个类大家可以自己去搜索下载)来将pathData封装成path,然后将path传入ProvinceItem类生成java bean类。下面我们来看一下这个Bean类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
路径
*/
private Path path;
/*
绘制颜色
*/
private int drawColor;

public ProvinceItem(Path path) {
this.path = path;
}

/**
* 自绘
* @param canvas
* @param paint
* @param isSelect
*/
public void draw(Canvas canvas, Paint paint, boolean isSelect) {
if (isSelect) {
//绘制背景
paint.setStrokeWidth(2);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
paint.setShadowLayer(8, 0, 0, 0xffffffff);
canvas.drawPath(path, paint);

//绘制省份
paint.clearShadowLayer();
paint.setColor(drawColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
canvas.drawPath(path, paint);
} else {
//没有被选择的时候绘制内容
paint.clearShadowLayer();
paint.setColor(drawColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
canvas.drawPath(path, paint);

//选择的时候绘制边界线
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0XFFEEEEEE);
canvas.drawPath(path, paint);
}
}
/*
判断是否点击了省份
*/
public boolean isTouch(int x, int y) {
RectF rectF = new RectF();
//就是用path的四个点生成一个rectf
path.computeBounds(rectF, true);
Region region = new Region();
//setPath 就是用path在region里面剪切出一个区域
region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
return region.contains(x, y);
}

这个类主要就是两个方法,draw方法和isTouch方法,下面来说这两个方法的作用:

draw

draw方法主要是传递三个参数来进行自绘:

  • canvas 画板
  • paint 画笔
  • isSelect 是否被选中

因为这个方法用来处理被点击时界面的变化,未选中和被选中的效果是不一样的。

此处可以自己来随便写。

isTouch

这个方法判断这个区域是否被选中,当然也可以有其他的方法来判断是否被点击,这里提供一种思路,主要是region里面的setPath方法,我们点进去源码看一下

1
2
3
4
5
6
7
8
9
/**
* Set the region to the area described by the path and clip.
* Return true if the resulting region is non-empty. This produces a region
* that is identical to the pixels that would be drawn by the path
* (with no antialiasing).
*/
public boolean setPath(Path path, Region clip) {
return nativeSetPath(mNativeRegion, path.readOnlyNI(), clip.mNativeRegion);
}

大概的意思就是用这个path在region裁剪出一个region,这个区域就是省份的区域,是不规则的。

解析完了之后就是第四步了,重写ondraw。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (itemList != null) {
canvas.save();
canvas.scale(SCALE_FATOR, SCALE_FATOR);
for (ProvinceItem item : itemList) {
//绘制未被选中
if (item != selectedItem) {
item.draw(canvas, paint, false);
}
}
//绘制选择的
if (selectedItem != null) {
selectedItem.draw(canvas, paint, true);
}

}
}

这里就是很简单的绘制。

第五步。重写onTouchEvent方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
   @Override
public boolean onTouchEvent(MotionEvent event) {
//将事件转交
return gestureDetectorCompat.onTouchEvent(event);
}

private void init(Context context) {
gestureDetectorCompat = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
handlerTouch(e.getX(), e.getY());
return super.onDown(e);
}
});
}

/**
* 处理触摸事件的方法
* @param x
* @param y
*/
private void handlerTouch(float x, float y) {
if (itemList != null) {
ProvinceItem temp = null;
for (ProvinceItem item : itemList) {
//要除以一个放大系数
if (item.isTouch((int) (x / SCALE_FATOR), (int) (y / SCALE_FATOR))) {
temp = item;
break;
}
}
if (temp != null) {
selectedItem = temp;
Toast.makeText(context, "You click me OVO", Toast.LENGTH_SHORT).show();
postInvalidate();
}
}
}

主要就是将down事件转交给gestureDetector来处理,然后写了处理触摸的方法,注意判断点击的xy值需要除以一个放大系数,因为前面放大了canvas,不然会点不到。

好了,以上就是主要的内容了,有什么想看的可以去github看源码,这也是我的学习笔记,发现svg制作复杂的自定义view真的是摔锅神器啊,开玩笑,是大大加快了开发效率。。。