天天看点

Android高级UI——SCV实现交互式地图

效果图

Android高级UI——SCV实现交互式地图

SVG 意为可缩放矢量图形(Scalable Vector Graphics),是使用 XML 来描述二维图形和绘图程序的语言;

使用 SVG 的优势在于:

1.SVG 可被非常多的工具读取和修改(比如记事本),由于使用xml格式定义,所以可以直接被当作文本文件打开,看里面的数据;

2.SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强,SVG 图就相当于保存了关键的数据点,比如要显示一个圆,需要知道圆心和半径,那么SVG 就只保存圆心坐标和半径数据,而平常我们用的位图都是以像素点的形式根据图片大小保存对应个数的像素点,因而SVG尺寸更小;

3.SVG 是可伸缩的,平常使用的位图拉伸会发虚,压缩会变形,而SVG格式图片保存数据进行运算展示,不管多大多少,可以不失真显示;

4.SVG 图像可在任何的分辨率下被高质量地打印;

5.SVG 可在图像质量不下降的情况下被放大;

6.SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图);

7.SVG 可以与 Java 技术一起运行;

8.SVG 是开放的标准;

9.SVG 文件是纯粹的 XML;

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():关闭路径(会自动绘制链接起点和终点)
           

思路

第一步 下载含有地图的 SVG

第二步 用http://inloop.github.io/svg2android/ 网站 将svg资源转换成相应的 Android代码

第三步 利用Xml解析SVG的代码 封装成javaBean 最重要的得到Path

第四步 重写OnDraw方法 利用Path绘制地图

第五步 重写OnTouchEvent方法,记录手指触摸位置,判断这个位置是否坐落在某个省份上

技术点

1、读取svg文件,解析出path数据(本例用到dom解析)

2、将path绘制到canvas画布上

3、根据view大小缩放画布,平移画布使图像位于view的中心显示

4、计算点击地图的坐标,根据此坐标判断是否某一区域被选中

开始实现

1、初始化

//本例用ProvinceModel定义每一个要绘制的区域
private ArrayList<ProvinceModel> mModelList = new ArrayList<>();
//定义颜色数组来显示不同区域的颜色
private int[] colors = {Color.parseColor("#8A2BE2"),Color.parseColor("#00BFFF")
            ,Color.parseColor("#87CEEB"),Color.parseColor("#87CEFA"),Color.parseColor("#87CEEB")};
           
private void init(){
    //此处必须添加,可以使用软件加速或硬件加速都可,如果不添加会出现很奇怪的现象
        setLayerType(View.LAYER_TYPE_SOFTWARE,null);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth();
        //用于缩放和平移操作
        mMatrix = new Matrix();
        mPaint.setAntiAlias(true);
    }
           

2、启动新线程读取svg文件

Thread MsgCodeThread = new Thread(){
        @Override
        public void run() {
            float left = ;
            float top = ;
            float right = ;
            float bottom = ;
            draw = false;
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            try {
            //读取raw文件夹下的svg文件
                InputStream is = mContext.getResources().openRawResource(R.raw.vector_map);
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(is);
                Element rootElement = document.getDocumentElement();
                //读取path 节点
                NodeList nodeList = rootElement.getElementsByTagName("path");
                for (int i = ; i < nodeList.getLength(); i++) {
                //此处用创建一个model来保存每个区域的color和path,实现绘制
                    ProvinceModel model = new ProvinceModel();
                    // Node转成Element
                    Element element = (Element) nodeList.item(i);
                    //读取pathData 节点
                    String pathData = element.getAttribute("d");
                    Path path = PathParser.createPathFromPathData(pathData);
                    RectF rectF=new RectF();
                    path.computeBounds(rectF,true);
                    left = left==?rectF.left:Math.min(rectF.left,left);
                    top = top==?rectF.top:Math.min(rectF.top,top);
                    right = right==?rectF.right:Math.max(rectF.right,right);
                    bottom = bottom==?rectF.bottom:Math.max(rectF.bottom,bottom);
                    model.setPath(path);
                    model.setColor(colors[i%colors.length]);
                    mModelList.add(model);
                }
                float width = right -left;
                float height = bottom-top;
                scale = mViewWidth/width;
                float scale2 = mViewHeight/height;
                scale = Math.min(scale,scale2);
                mRectFMap = new RectF(left,top,right,bottom);
                mHandler.sendEmptyMessage();

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
           
//此处用来计算整个map区域的边界,为图形的缩放做准备
     RectF rectF=new RectF();
     path.computeBounds(rectF,true);
     //通过循环比较rectF的边界来确定map的边界
     left = left==?rectF.left:Math.min(rectF.left,left);
     top = top==?rectF.top:Math.min(rectF.top,top);
     right = right==?rectF.right:Math.max(rectF.right,right);
     bottom = bottom==?rectF.bottom:Math.max(rectF.bottom,bottom);
           
//map的宽高
 float width = right -left;
 float height = bottom-top;
 //计算宽高的缩放比
 scale = mViewWidth/width;
 float scale2 = mViewHeight/height;
 //去最小的缩放比
 scale = Math.min(scale,scale2);
 //此处记录map的RectF的区域,后边有用
 mRectFMap = new RectF(left,top,right,bottom);
           
//发送handler还是重绘
 mHandler.sendEmptyMessage();
           

mViewWidth 、mViewHeight要在onMeasure以后获取。

3、重写onDraw方法开始绘制

mMatrix.reset();
 mMatrix.setScale(scale,scale);
 if(mRectFMap != null){
     float centerX = mRectFMap.centerX();
     float centerY = mRectFMap.centerY();
     tranX = mViewWidth/-centerX*scale;
     tranY = mViewHeight/-centerY*scale;
     //将画布平移到view 的中心
     mMatrix.postTranslate(tranX,tranY);//后乘可以理解为先画图形再平移
//            mMatrix.preTranslate(mViewWidth/2-centerX*scale,mViewHeight/2-centerY*scale);//前乘可以理解为先平移再画图形
 }
 canvas.setMatrix(mMatrix);
           

设置缩放和偏移量的matrix

for(ProvinceModel model:models){
    if(model.isSelected(mTouchedX,mTouchedY)){
        model.draw(canvas,mPaint,true);
        continue;
    }
    model.draw(canvas,mPaint,false);
}
           

遍历集合将canvas和paint传入到model,调用 model.draw(canvas,mPaint,true);实现绘制,此处选中和未选中的区域使用不同的绘制方式,model.isSelected(mTouchedX,mTouchedY)此方法传入点击位置由model自己判断是否在相应区域中

4、ProvinceModel 类的几个关键方法

public boolean isSelected(int x,int y){
        Region region = new Region();
        RectF rectF = new RectF();
        path.computeBounds(rectF,true);
        region.setPath(path,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
        return region.contains(x,y);
    }
           

此方法参数是传入的坐标点,用来判断该坐标点是否在此区域内 ,Region除支持矩形外,还可以使用Path来定义一个任意区域,然后再组合成复杂形状,computeBounds(RectF bounds,boolean exact):计算path所在区域,setPath(Path path, Region clip)//将path和clip的两个区域取交集

public void draw(Canvas canvas, Paint paint,boolean isSelected){
        paint.reset();
        paint.setStyle(Paint.Style.STROKE);
        if(isSelected){
            paint.setStrokeWidth();
            paint.setColor(Color.GRAY);
        }else{
            paint.setStrokeWidth();
            paint.setColor(Color.WHITE);
        }

        paint.setShadowLayer(,,,Color.WHITE);
        canvas.drawPath(path,paint);

        paint.clearShadowLayer();
        if(isSelected){
            paint.setColor(Color.parseColor("#CCFF0000"));
        }else{
            paint.setColor(color);
        }
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setStrokeWidth();
        canvas.drawPath(path,paint);
    }
           

此方法用来真正的绘制path,先绘制一层边界,后绘制填充区域,根据是否被选中设置不同的画笔颜色和填充颜色来实现不同的显示效果

5、重写onTouchEvent点击事件,确定缩放和平移后的坐标点之后重绘

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN){
            mTouchedX = (int) ((event.getX()-tranX)/scale);
            mTouchedY = (int) ((event.getY()-tranY)/scale);
            invalidate();
        }
        return true;
    }
           

本人功底浅薄,欢迎指正,有什么问题可以给我留言

源代码地址https://github.com/goufeng/SVCMapView 本项目实现了mapview根据view的大小自动调整缩放和位置,使mapview可以始终在view中心完全显示