最近发现可以用SVG来实习非常复杂的自定义View,可以说是非常的厉害了。只要你看到不想做的很复杂的设计图,你就让美工妹子给你来一张SVG吧,还可以趁机和她聊下天。
我们就来画一个可交互 的中国台湾地图吧,首先我们来理一下做这种自定义View的步骤。
下载含有中国地图的 SVG
用**此 **网站 将svg资源转换成相应的 Android代码
利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path
重写OnDraw方法 利用Path绘制中国地图
重写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; } 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.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); }
这个类主要就是两个方法,draw方法和isTouch方法,下面来说这两个方法的作用:
draw draw方法主要是传递三个参数来进行自绘:
canvas 画板
paint 画笔
isSelect 是否被选中
因为这个方法用来处理被点击时界面的变化,未选中和被选中的效果是不一样的。
此处可以自己来随便写。
isTouch 这个方法判断这个区域是否被选中,当然也可以有其他的方法来判断是否被点击,这里提供一种思路,主要是region里面的setPath方法,我们点进去源码看一下
1 2 3 4 5 6 7 8 9 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); } }); } 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真的是摔锅神器啊,开玩笑,是大大加快了开发效率。。。