自定義View--加載SVG地圖的控件MapView

特別說明

  • 當前博客平臺賬號已廢棄薪贫,如果有使用細節(jié)問題請前往我個人博客平臺 HuRuWo的技術(shù)小站進行討論交流絮短。直接在其他平臺留言可能無法及時得到回復(fù)窘问。
  • 文章首發(fā)于個人博客HuRuWo的技術(shù)小站,如果本文非vip用戶無法完全瀏覽或者圖片無法打開臼婆,可前往個人博客文章地址查看文章并留言討論。

  • 本篇文章個人博客文章地址自定義View--加載SVG地圖的控件MapView,點擊鏈接直接前往恰起。

  • 更多技術(shù)文章訪問本人博客HuRuWo的技術(shù)小站修械,包括 Electron從零開發(fā) Android 逆向 app 微信數(shù)據(jù)抓取 抖音數(shù)據(jù)抓取 閑魚數(shù)據(jù)抓取 小紅書數(shù)據(jù)抓取 其他軟件爬蟲 等技術(shù)文章

前言

最近制作一款和地圖相關(guān)的軟件,需要在地圖上點擊不同的色塊進入不同的子地圖检盼。并且需要在地圖上添加標志物肯污。

顯然當前的第三方地圖sdk無法完成,所以需要自定義View梯皿。

效果展示

gif.gif
click.gif

思路

-- 地圖

  1. 解析SVG獲得地圖的path
  2. 自定義View繪制path
  3. 3.點擊事件 計算點位置所落地點
  4. 由標記的出每個區(qū)域中心的位置

-- 標志物

需要覆蓋在view的指定位置仇箱,有可能是圖片/View或者其他東西

MapView

解析SVG圖片

首先把SVG轉(zhuǎn)化為

通過Android SVG to VectorDrawable
轉(zhuǎn)化為android格式的文件,復(fù)制后導(dǎo)入工程东羹。

編寫工具類解析svg的path

這個方法在包android.support.v4.graphics下面

原理則是:
取出path的點剂桥,按svg圖片規(guī)則讀取成android里面的繪圖規(guī)則連線,成為一個整的path属提。
PathParser

自定義View繪制取出來的Path

這個就很簡單了,只需要使用canvas.drawPath

核心ondraw:

        canvas.save();
        canvas.scale(scale, scale, 0, 0); //中心縮放 
        for (PathItem pathItem : pathItems) {
            if ((pathItems.indexOf(pathItem) > pathItems.size()/2-1)) {
            } else {
                pathItemDraw(canvas, pathItem);
            }
        }
        /**
         * 文字繪制
         */
        points.clear();
        for (PathItem pathItem : pathItems) {
            if (pathItems.indexOf(pathItem) > pathItems.size()/2-1) {
                pathCenterText(canvas, pathItem);
            } else {
                pathBoder(canvas, pathItem);
            }
        }

        if (!ispos) {
            if (onPathDrawListener != null) {
                onPathDrawListener.onPathDrawFinish(getPoints());
            }
            ispos = !ispos;
        }

        canvas.restore();

其中調(diào)用的三個繪圖方法:

 public void pathItemDraw(final Canvas canvas, PathItem pathItem) {
        if (pathItem.getIsSelect()) {
            //首先繪制選中的背景陰影
            paintMap.clearShadowLayer();
            paintMap.setShadowLayer(8, 0, 0, Color.BLACK);
            canvas.drawPath(pathItem.getPath(), paintMap);
            //繪制具體顯示的
            paintMap.clearShadowLayer();
            paintMap.setColor(Color.WHITE);
            canvas.drawPath(pathItem.getPath(), paintMap);

        } else {
            //繪制具體顯示的
            paintMap.clearShadowLayer();
            paintMap.setColor(Color.parseColor(pathItem.getColor()));
            canvas.drawPath(pathItem.getPath(), paintMap);
        }


    }
    public void pathCenterText(Canvas canvas, PathItem pathItem) {

        PathMeasure measure = new PathMeasure(pathItem.getPath(), false);
        float[] pos1 = new float[2];
        measure.getPosTan(measure.getLength() / 2, pos1, null);

        /**
         * 計算字體的寬高
         */
        Rect rect = new Rect();
        paintText.getTextBounds(pathItem.getTitle(), 0, pathItem.getTitle().length(), rect);

        int w = rect.width();
        int h = rect.height();


        canvas.drawText(pathItem.getTitle(), pos1[0] - w / 2, pos1[1] + h, paintText);


        /**
         * 兩點求中點
         */
        float[] pos2 = new float[2];
        measure.getPosTan(0, pos2, null);

        float[] point = new float[2];

        point[0] = (pos1[0] + pos2[0]) / 2;
        point[1] = (pos1[1] + pos2[1]) / 2;


        points.add(point);

    }
    public void pathBoder(Canvas canvas, PathItem pathItem) {

        canvas.drawPath(pathItem.getPath(), paintBoder);
    }

path路徑的點擊事件

1.定義點擊事件回調(diào):

public interface OnPathClickListener {
        void onPathClick(PathItem p);
    }

2.View點擊事件輔助工具GestureDetector

mDetector = new GestureDetector(context, this);
        mDetector.setOnDoubleTapListener(this);

3.View繼承接口

包括普通點擊事件以及單雙擊事件

implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener

4.方法實現(xiàn)

 @Override
    public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onSingleTapConfirmed" + motionEvent.getY() + "---" + motionEvent.getY());
        float x = (motionEvent.getX() - has_move_x) / scale;
        float y = (motionEvent.getY() - has_move_y) / scale;
        for (PathItem pathItem : pathItems) {

            if (BitmapUtil.isTouch((int) x, (int) y, pathItem.getPath())) {
                if (onPathClickListener != null) {
                    onPathClickListener.onPathClick(pathItem);
                }
                pathItem.setIsSelect(true);
            } else {
                pathItem.setIsSelect(false);
            }

        }
        invalidate();
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onDoubleTap" + motionEvent.getY() + "---" + motionEvent.getY());
        /**
         * 雙擊放大
         */
        scale = scale * 1.1f;
        invalidate();
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onDoubleTapEvent" + motionEvent.getY() + "---" + motionEvent.getY());
        return false;
    }

    @Override
    public boolean onDown(MotionEvent motionEvent) {

        Log.e("觸摸點擊", "onDown" + motionEvent.getY() + "---" + motionEvent.getY());

        return true;
    }

    @Override
    public void onShowPress(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onShowPress" + motionEvent.getY() + "---" + motionEvent.getY());
    }

    /**
     * 這個事件 在雙擊事件也會觸發(fā) 所以為了區(qū)分 單擊事件應(yīng)該 放在onSingleTapConfirmed 中
     *
     * @param motionEvent
     * @return
     */
    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onSingleTapUp" + motionEvent.getY() + "---" + motionEvent.getY());

        return false;
    }


    @Override
    public void onLongPress(MotionEvent motionEvent) {
        Log.e("觸摸點擊", "onLongPress" + motionEvent.getY() + "---" + motionEvent.getY());

        /**
         * 長按恢復(fù)
         */
        has_move_x = 0;
        has_move_y = 0;
        move_x = 0;
        move_y = 0;

        scale = getMinScale();

        invalidate();

    }

   

可以看到核心的單擊事件权逗,判斷是否屬于某個path區(qū)域,并回調(diào)。

if (BitmapUtil.isTouch((int) x, (int) y, pathItem.getPath())) {
                if (onPathClickListener != null) {
                    onPathClickListener.onPathClick(pathItem);
                }
                pathItem.setIsSelect(true);
            } else {
                pathItem.setIsSelect(false);
            }

5.最后記得把view觸控事件交給GestureDetector

 /**
     * ---------------------手勢的處理---------------
     **/
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mDetector.onTouchEvent(event); //把手勢相關(guān)操作返回給 手勢操控類
    }

以上就是繪制地圖的方法冤议,接下繪制地圖標志物斟薇。

地圖標志物

自定義ViewGroup繪制遮蓋物

MapView獲取區(qū)域中點的位置:

寫一個getMapPoint方法,獲取各個市區(qū)中心點位置恕酸,方便獲取遮蓋物堪滨。

然后在MapView的父布局添加子View

1.定義繪圖完成,計算完成回調(diào):

   private OnPathDrawListener onPathDrawListener;
    public interface OnPathDrawListener {
        void onPathDrawFinish(List<float[]> pos);
    }

計算地圖中的市區(qū)中點

float[] pos2 = new float[2];
        measure.getPosTan(0, pos2, null);

        float[] point = new float[2];

        point[0] = (pos1[0] + pos2[0]) / 2;
        point[1] = (pos1[1] + pos2[1]) / 2;


        points.add(point);

if (onPathDrawListener != null) {
                onPathDrawListener.onPathDrawFinish(getPoints());
            }

獲得回調(diào)蕊温,繪制遮蓋View

ztemap.setOnPathDrawListener(new ZTEMapView.OnPathDrawListener() {
          @Override
          public void onPathDrawFinish(List<float[]> pos) {
              for (float[] point : points) {
            PointView PointView = new PointView(getContext());
            PointView.scrollTo((int) -point[0]+width/2, (int) -point[1]+hight/2);
            addView(PointView);
        }
          }
      });

PointView為自定義的波浪View


到此為止袱箱,整個圖繪制完成了。

View加載優(yōu)化

1.優(yōu)化地圖資源加載速度

由于每次從xml資源取得path路徑的string是非常耗時的io操作义矛。所以可以使用數(shù)據(jù)庫緩存得到的path路徑資源发笔。只需要第一次加載之后,都從數(shù)據(jù)庫獲取凉翻。
具體看demo即可了讨。

2.標志物的加載需要在地圖的加載之后,所以需要設(shè)置延遲制轰,以防界面卡頓前计。具體邏輯及時監(jiān)聽地圖的

addOnGlobalLayoutListener

3.動畫繪制的優(yōu)化

項目地址

HuRuWo的MapView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市艇挨,隨后出現(xiàn)的幾起案子残炮,更是在濱河造成了極大的恐慌,老刑警劉巖缩滨,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件势就,死亡現(xiàn)場離奇詭異泉瞻,居然都是意外死亡,警方通過查閱死者的電腦和手機苞冯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門袖牙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舅锄,你說我怎么就攤上這事鞭达。” “怎么了皇忿?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵畴蹭,是天一觀的道長。 經(jīng)常有香客問我鳍烁,道長叨襟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任幔荒,我火速辦了婚禮糊闽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爹梁。我一直安慰自己右犹,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布姚垃。 她就那樣靜靜地躺著念链,像睡著了一般。 火紅的嫁衣襯著肌膚如雪积糯。 梳的紋絲不亂的頭發(fā)上钓账,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音絮宁,去河邊找鬼。 笑死服协,一個胖子當著我的面吹牛绍昂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偿荷,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼窘游,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跳纳?” 一聲冷哼從身側(cè)響起忍饰,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寺庄,沒想到半個月后艾蓝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體力崇,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年赢织,在試婚紗的時候發(fā)現(xiàn)自己被綠了亮靴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡于置,死狀恐怖茧吊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情八毯,我是刑警寧澤搓侄,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站话速,受9級特大地震影響讶踪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尿孔,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一俊柔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧活合,春花似錦雏婶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至告嘲,卻和暖如春错维,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背橄唬。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工赋焕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仰楚。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓隆判,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僧界。 傳聞我的和親對象是個殘疾皇子侨嘀,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容