SVG实现中国地图

SVG实现中国地图_第1张图片

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信息。

利用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 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下载

你可能感兴趣的:(android开发,android)