Android 使用svg构造交互式中国地图

概念

什么是svg

即Scalable Vector Graphics 可伸缩矢量图形
SVG的W3C的解释: http://www.w3school.com.cn/svg/svg_intro.asp
什么是矢量图像,什么是位图图像?
1、矢量图像:SVG是W3C 推出的一种开放标准的文本式矢量图形描述语言,他是基于XML的、专门为网络而设计的图像格式,
SVG是一种采用XML来描述二维图形的语言,所以它可以直接打开xml文件来修改和编辑。
2、位图图像:位图图像的存储单位是图像上每一点的像素值,因而文件会比较大,像GIF、JPEG、PNG等都是位图图像格式。

Vector

在Android中指的是Vector Drawable,也就是Android中的矢量图,
可以说Vector就是Android中的SVG实现(并不是支持全部的SVG语法,现已支持的完全足够用了)

补充:Vector图像刚发布的时候,是只支持Android 5.0+的,自从AppCompat 23.2之后,Vector可以使用于Android 2.1以上的所有系统,
            只需要引用com.android.support:appcompat-v7:23.2.0以上的版本就可以了。(所谓的兼容也是个坑爹的兼容,即低版本非真实使用SVG,而是生成PNG图片)
  1) Vector 语法简介
        通过使用它的Path标签,几乎可以实现SVG中的其它所有标签,虽然可能会复杂一点,
        但这些东西都是可以通过工具来完成的,所以,不用担心写起来会很复杂。
        (1)Path指令解析如下所示:
            M = moveto(M X,Y) :将画笔移动到指定的坐标位置,相当于 android Path 里的moveTo()
            L = lineto(L X,Y) :画直线到指定的坐标位置,相当于 android Path 里的lineTo()
            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):弧线 ,相当于arcTo()
            Z = closepath():关闭路径(会自动绘制链接起点和终点)

            注意,’M’处理时,只是移动了画笔, 没有画任何东西。

    注意:1.关于这些语法,开发者不需要全部精通,而是能够看懂即可,这些path标签及数据生成都可以交给工具来实现。
        (一般美工来帮你搞定!PS、Illustrator等等都支持导出SVG图片)

            2.程序员:没必要去学习使用这些设计工具,开发者可以利用一些工具,自己转换一些比较基础的图像,
                如:http://inloop.github.io/svg2android/ 
            3.还可以使用SVG的编辑器来进行SVG图像的编写,例如:http://editor.method.ac/
            (绝配!可以先用http://editor.method.ac/ 生成SVG图片,然后用http://inloop.github.io/svg2android/ 生成 VectorDrawable xml代码)
            4.使用AndroidStudio插件完成SVG添加(Vector Asset Studio)
                详细:http://www.jianshu.com/p/d6c39f2dd5e7
                AS会自动生成兼容性图片(高版本会生成xxx.xml的SVG图片;低版本会自动生成xxx.png图片)
            5.有些网站可以找到SVG资源
                SVG下载地址: http://www.shejidaren.com/8000-flat-icons.html
                              http://www.flaticon.com/
                              
                              http://www.iconfont.cn/plus --- 阿里巴巴
                              
                图片转成SVG https://vectormagic.com/

 总结如下:

SVG的demo实现

类似下面的效果:

实现思路:
第一步 下载含有中国地图的 SVG
第二步 用http://inloop.github.io/svg2android/ 网站 将svg资源转换成相应的 Android代码
第三步 利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path
第四步 重写OnDraw方法 利用Path绘制中国地图
第五步 重写OnTouchEvent方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上。代码如下:

package com.xifei.svgmapdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import androidx.core.graphics.PathParser;

public class MapView extends View {
    //上下文
    private Context context;
    //画笔
    private Paint paint;
    //适配比例
    private float scale = 1.0f;
    //矩形对象
    private RectF totalRect;
    //定义一个装载所有省份的集合
    List<ProviceItem> itemList;
    //绘制地图的颜色
    private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};
    //是否XML已经解析完毕
    private boolean isEnd = false;
    //当前选中的省份
    private ProviceItem select;


    public MapView(Context context) {
        super(context);
    }

    public MapView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        paint = new Paint();
        paint.setAntiAlias(true);
        loadThread.start();

    }

    public MapView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private Thread loadThread = new Thread(new Runnable() {
        @Override
        public void run() {
            //定义一个输入流对象去加载xml文件
            InputStream inputStream = context.getResources().openRawResource(R.raw.china);
            //定义一个装载所有省份的集合
            List<ProviceItem> list = new ArrayList<>();
            try {
                //获取到解析器的工厂类
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                //获取到SVG图形的XML对象
                Document parse = builder.parse(inputStream);
                //获取都节点目录
                Element documentElement = parse.getDocumentElement();
                //通过Element获取到所有path节点的集合
                NodeList items = documentElement.getElementsByTagName("path");
                //定义四个点
                float left= -1;
                float bottom= -1;
                float top= -1;
                float right = -1;
                //遍历所有的path节点
                for(int x=0;x<items.getLength();x++){
                    //获取到每一个path节点
                    Element element = (Element) items.item(x);
                    String pathData = element.getAttribute("android:pathData");
                    Path path = PathParser.createPathFromPathData(pathData);
                    ProviceItem proviceItem = new ProviceItem(path);
                    list.add(proviceItem);

                    //初始化每个省份的矩形
                    RectF rect = new RectF();
                    //获取到每个省份的边界
                    path.computeBounds(rect,true);

                    //获取到left最小的值
                    left = left == -1?rect.left: Math.min(left,rect.left);
                    //获取right最大值
                    right = right==-1?rect.right: Math.max(right,rect.right);
                    //遍历取出每个path中的top取所有的最小值
                    top = top == -1 ? rect.top : Math.min(top, rect.top);
                    //遍历取出每个path中的bottom取所有的最大值
                    bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
                }
                //封装地图的矩形
                totalRect = new RectF(left,top,right,bottom);
                itemList = list;
                handler.sendEmptyMessage(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });


    /**
     * 设置省份的颜色
     */
    Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            if(itemList == null || itemList.size()<=0){
                return;
            }
            int size = itemList.size();
            for(int x=0;x<size;x++){
                int color = Color.WHITE;
                int flag = x % 4;
                switch (flag){
                    case 1:
                        color = colorArray[0];
                        break;
                    case 2:
                        color = colorArray[1];
                        break;
                    case 3:
                        color = colorArray[2];
                        break;
                    default:
                        color = Color.CYAN;
                        break;
                }
                itemList.get(x).setDrawColor(color);
            }
            isEnd = true;
            measure(getMeasuredWidth(),getMeasuredHeight());
            //调用绘制
            postInvalidate();
        }
    };

    @Override
    protected void onDraw(Canvas canvas) {
        if(!isEnd){
            return;
        }
        if(itemList !=null && itemList.size()>0){
            canvas.save();
            canvas.scale(scale,scale);
            for (ProviceItem proviceItem : itemList) {
                if(select == proviceItem){
                    proviceItem.drawItem(canvas,paint,true);
                }else{
                    proviceItem.drawItem(canvas,paint,false);
                }
            }
        }
        super.onDraw(canvas);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取到当前控件宽高值
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if(!isEnd){
            setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY));
            return;
        }
        if(totalRect !=null){
            //获取到地图的矩形的宽度
            double mapWidth = totalRect.width();
            //获取到比例值
            scale = (float) (width/mapWidth);
        }
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY));
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将当前手指触摸到位置传过去  判断当前点击的区域
        handlerTouch(event.getX(),event.getY());
        return super.onTouchEvent(event);
    }

    /**
     * 判断区域
     * @param x
     * @param y
     */
    private void handlerTouch(float x, float y) {
        //判空
        if(itemList ==null || itemList.size() ==0){
            return;
        }
        //定义一个空的被选中的省份
        ProviceItem selectItem =null;
        for (ProviceItem proviceItem : itemList) {
            //入股点击的是这个省份的范围之内 就把当前省份的封装对象绘制的方法 传一个true
            if(proviceItem.isTouch(x/scale,y/scale)){
                selectItem = proviceItem;
            }
        }
        if(selectItem !=null){
            select = selectItem;
            postInvalidate();
        }
    }
}

JavaBean代码如下:

package com.xifei.svgmapdemo;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;

/**
 * 省份的封装类
 */
public class ProviceItem {
    //path对象
    private Path path;
    //绘制的颜色
    private int drawColor;

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

    public void setDrawColor(int drawColor){
        this.drawColor = drawColor;
    }

    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(0,0,0,0xffffff);
            canvas.drawPath(path,paint);

            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.BLACK);
            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);
            canvas.drawPath(path,paint);
        }
    }

    public boolean isTouch(float x,float y){
        //创建一个矩形
        RectF rectF = new RectF();
        //获取到当前省份的矩形边界
        path.computeBounds(rectF, true);
        //创建一个区域对象
        Region region = new Region();
        //将path对象放入到Region区域对象中
        region.setPath(path, new Region((int)rectF.left, (int)rectF.top,(int)rectF.right, (int)rectF.bottom));
        //返回是否这个区域包含传进来的坐标
        return region.contains((int)x,(int)y);
    }

}

demo下载地址 : https://download.csdn.net/download/xifei66/13107152

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值