天天看點

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中心完全顯示