赞
踩
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
第一步 下载含有中国地图的 SVG
这里附一个地图资源: https://www.amcharts.com/dl/javascript-maps/ ,这里面包含世界各个国家的SVG地图,各个省份地图
第二步 将 SVG 资源转换成相应的 Android 代码
用http://inloop.github.io/svg2android/ 网站,将 SVG 转化成 Xml文件,放置在 /res/raw/ 下。
第三步 利用 Xml 解析 SVG 的代码 封装成 JavaBean 最重要的得到 Path
利用 Xml 解析,把中国地图的 Path 封装成一个个省的 JavaBean。
第四步 重写 OnDraw 方法 利用 Path 绘制中国地图
第五步 重写 OnTouchEvent 方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上
我们先来封装一个省份的 JavaBean,以便对各个省份进行绘制。
public class Provice { /** * 绘制路径 */ protected Path path; /** * 绘制颜色 */ private int drawColor; public Provice(Path path) { this.path = path; } 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,0xffffff); 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.setStrokeWidth(1); paint.setStyle(Paint.Style.FILL); paint.setColor(drawColor); canvas.drawPath(path, paint); //非选中时,绘制地图 paint.setStyle(Paint.Style.STROKE); int strokeColor = 0xFFD0E8F4; paint.setColor(strokeColor); canvas.drawPath(path, paint); } } /** * 是否被选中 */ public boolean isSelect(int x, int y) { //构造一个区域对象 RectF rectF=new RectF(); //计算控制点的边界 path.computeBounds(rectF,true); Region region=new Region(); region.setPath(path,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom)); return region.contains(x,y); } public Path getPath() { return path; } public void setPath(Path path) { this.path = path; } public int getDrawColor() { return drawColor; } public void setDrawColor(int drawColor) { this.drawColor = drawColor; } }
这段代码相对还是比较简单,在 draw 中不论选中与否都进行两次绘制,是因为当边沿颜色设置与中间颜色不一样的话,是没办法直接进行绘制,只能分两次进行边沿和中间内容的绘制。
isSelect 就是对一个点是否在一个区域中进行判断,这个方法比较常见,就不具体介绍了。拿 Path 路径与该路径边界组成的矩形进行相裁剪,获取到的就是该 Path 所围成的区域。
这个主要是分两部,一个是读取 Xml 中 Path 的数据,即对 Xml 数据进行解析。二是将获取到的 Path 路径数据封装成 Provice 类。
InputStream inputStream = context.getResources().openRawResource(R.raw.china_svg); try { //取得 DocumentBuilderFactory 实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //从 factory 获取 DocumentBuilder 实例 DocumentBuilder builder = factory.newDocumentBuilder(); //解析输入流,得到 Document 实例 Document doc = builder.parse(inputStream); Element rootElement = doc.getDocumentElement(); NodeList items = rootElement.getElementsByTagName("path"); for (int i = 0; i < items.getLength(); i ++) { Element element= (Element) items.item(i); String pathData=element.getAttribute("android:pathData"); } } catch (Exception e) { e.printStackTrace(); }
这即是对一个 Xml 数据进行读取的方法,跟 File 读写一样,都是固定套路。
由 Xml 解析器解析出来的 Path 数据是一个字符串,我们需要把这串字符串解析成对应的 Path ,然后调用构造函数创建对应的 Provice。
选取其中一个省份数据进行分析:
<path
android:fillColor="#CCCCCC"
android:strokeColor="#ffffff"
android:strokeWidth="0.5"
android:pathData="M546.46,257.82L546.2,258.07L545.19,257.69L545.32,258.18L543.93,257.5L543.54,257.65L542.41,258.89L541.38,259.38L542.02,260.56L540.61,260.4L539.44,259.53L539.28,258.7L538.66,258.32L537.92,258.56L536.13,258.13L535.82,258.52L535.34,258.01L535.11,258.57L534.23,258.84L533.87,259.42L533.2,259.42L532.34,257.98L530.81,258.02L529.74,257.1L529.84,255.69L529,255.13L530.84,254.47L530.2,254L530.15,252.87L528.99,252.2L529.29,251L531.38,249.47L533.13,249.19L533.53,248.49L534.28,249.13L534.78,247.81L535.79,247.12L535.17,245.59L534.67,245.52L533.4,244.1L533.46,243.33L532.94,243.15L533.23,242.5L535.15,241.24L536.1,241.87L537.59,241.28L539.29,238.42L540,238.94L541.47,238.45L542.04,238.74L542.1,238.37L540.49,236.54L540.53,236.05L540.92,235.88L541.32,236.49L542.23,236.64L541.97,235.28L543.78,235.25L543.95,234.15L544.4,233.9L545.04,234.33L544.79,235.26L545.4,236.52L546.36,237.49L547.05,237.66L548.92,239.98L550.77,239.84L552.86,240.63L554.07,240.2L555.26,240.44L555.25,240.92L554.58,240.66L554.36,240.98L554.53,241.89L552.78,241.9L551.97,242.41L552.11,242.94L551.46,243.23L551.9,244.24L551.74,245.25L553.34,247.62L553.79,247.69L553.79,247.69L553.79,248.62L551.45,250.07L551.45,250.07L549.71,250.41L548.96,250.95L548.26,250.65L546.19,250.97L545.91,251.98L546.21,253.02L547.91,253.88L548.31,254.99L547.73,255.44L547.66,256.41L547.66,256.41L547.72,256.71L547.07,256.96z" />
地图的 Path 是由很多条短的直线连接而成的,我们解析 Xml 获取到数据是表示这些连接点的字符串,需要把这个字符串转化成对应的 Path。网络上有提供对应的的工具类,当然也可以自己进行代码编写解析,代码较长,但不是很难,这边也不展示。
考虑到读取地图数据是一个耗时的操作,故另起一个线程进行数据的读取以及解析。
最终代码:
private Thread loadThread=new Thread(){ @Override public void run() { InputStream inputStream = context.getResources().openRawResource(R.raw.china_svg); try { //取得 DocumentBuilderFactory 实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //从 factory 获取 DocumentBuilder 实例 DocumentBuilder builder = factory.newDocumentBuilder(); //解析输入流,得到 Document 实例 Document doc = builder.parse(inputStream); Element rootElement = doc.getDocumentElement(); NodeList items = rootElement.getElementsByTagName("path"); for (int i = 0; i < items.getLength(); i ++) { Element element= (Element) items.item(i); String pathData=element.getAttribute("android:pathData"); Path path = PathParser.createPathFromPathData(pathData); proviceList.add(new Provice(path)); } } catch (Exception e) { e.printStackTrace(); } handler.sendEmptyMessage(0); } }; private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFB0D7F8}; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (proviceList == null) { return; } for (int i = 0; i < proviceList.size(); i++) { int color; int flag = i %4; switch (flag) { case 1: color = colorArray[0]; break; case 2: color = colorArray[1]; break; case 3: color = colorArray[2]; break; default: color = colorArray[3]; break; } proviceList.get(i).setDrawColor(color); } postInvalidate(); } };
PathParser 即是 Path 路径字符串转化为 Path 对象的工具类。在需要加载地图的时候,启动这个线程即可。
把数据解析成 JavaBean 之后,需要通知进行刷新操作。在这里,我们对各个省份的绘制颜色进行随机设置,实际中可以根据某一数据进行颜色绘制的选择。
onDraw()一直是核心重点。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (proviceList != null) { //绘制没有被选择的省份 for (Provice item : proviceList) { if (item != selectItem) { item.draw(canvas, paint, false); } } //绘制选中的省份 if (selectItem != null) { selectItem.draw(canvas, paint, true); } } }
onDraw()分别进行了选中与未选中省份的绘制,由于前面已经封装好 Provide 类的绘制方法 draw(),这边直接调用即可。
注:必须先绘制未选中的省份,否则在选中与未选中的边沿上,未选中的边沿颜色会把选中的边沿颜色给覆盖掉。
上面已经把 SVG 的地图数据转化为 Path 绘制出来,接下来需要对屏幕的触摸事件进行处理,选中对应区域的省份,重绘地图。
@Override public boolean onTouchEvent(MotionEvent event) { if (proviceList != null) { Provice provice = null; for ( Provice item : proviceList) { if (item.isSelect((int) (event.getX()), (int) (event.getY()))) { provice = item; break; } } if (provice != null) { selectItem = provice; postInvalidate(); } } return true; }
OnTouchEvent() 方法也比较简单,最开始的时候省份已经封装了判断点是否在该省份 Path 路径内的方法,所以这边只要遍历省份,调用该方法即可,最后进行重绘。
这时候基本实现了大体功能:把 SVG 地图数据绘制出来,实现了点击选中功能。
效果无法在这里展示了,见谅。
可以在 onDraw 方法前后记录时间去获取绘制地图的所需要的时间,是在0~1 毫秒之间,我们初始化的时候把 Path 保存在 List 集合里面,后面只需要再次绘制尽可,绘制的速度是特别快的。
这里,xml 的宽高都是设置为 match_parent,可以发现,屏幕宽度还是过小,导致地图无法完全显示,所以还需要重写 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 以及对地图进行缩放。
首先,要确认一下过去的 SVG 中国地图的具体边界,在 Provice 类的 isSelect(int x, int y) 就有获取各个省份的边界,可以在这边进行一个测试,记录边界值,并取最值,获取中国地图的边界。这边用的边界值为:左:0 上:0 右:773 下:568。即中国地图宽度为 773,高度为 568。
private static int Mleft=0, Mright=0, Mtop=0, Mbotton=0; public boolean isSelect(int x, int y) { //构造一个区域对象 RectF rectF = new RectF(); // 计算控制点的边界 path.computeBounds(rectF,true); Region region = new Region(); region.setPath(path, new Region((int)rectF.left, (int)rectF.top, (int)rectF.right, (int)rectF.bottom)); if (rectF.right > Mright) { Mright = (int)rectF.right; } if (rectF.bottom > Mbotton) { Mbotton = (int)rectF.bottom; } System.out.println( Mright + " " + Mbotton); return region.contains(x,y); }
加的这些是为了计算中国地图大小,实际不需要。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
scale = Math.min(width/mapWidth, height/mapHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (proviceList != null) {
canvas.scale(scale, scale);
for (Provice item : proviceList) {
if (item != selectItem) {
item.draw(canvas, paint, false);
}
}
if (selectItem != null) {
selectItem.draw(canvas, paint, true);
}
}
}
@Override public boolean onTouchEvent(MotionEvent event) { if (proviceList != null) { Provice provice = null; for ( Provice item : proviceList) { if (item.isSelect((int) (event.getX() / scale), (int) (event.getY() / scale))) { provice = item; break; } } if (provice != null) { selectItem = provice; postInvalidate(); } } return true; }
代码连接:http://download.csdn.net/download/qq_18983205/9956046
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。