使用SVG打造可交互的地圖控件

無圖無真相递礼,先上圖

demo.gif

這個(gè)效果看上去很高大上楼吃,通過常規(guī)手段來實(shí)現(xiàn)的難度很大友雳,使用SVG技術(shù)來實(shí)現(xiàn)非常合適。
下面分析一下實(shí)現(xiàn)思路:
1)不規(guī)則的區(qū)域需要使用SVG Path
2)地圖控件肯定是Canvas來繪制的自定義控件
3)點(diǎn)擊變色使用Android原生的觸摸事件即可解決

地圖的SVG矢量圖可以通過網(wǎng)絡(luò)找到資源署辉,再通過處理使之變成Android系統(tǒng)可以使用的矢量圖(本質(zhì)上是把SVG的標(biāo)簽進(jìn)行簡(jiǎn)化)栅受,下面以臺(tái)灣地圖為例:fillColor定義Path填充的顏色将硝,strokeColor定義Path邊界的顏色,strokeWidth定義Path邊界的寬度屏镊,pathData定義Path實(shí)際的路徑依疼,在本文中也就是地圖的路徑,其中一個(gè)城市的Path路徑如下:

path
        android:fillColor="#CCCCCC"
        android:strokeColor="#ffffff"
        android:strokeWidth="0.5"
        android:pathData="M573.31,330.07L570.81,329.19L570.81,329.19L569.19,331.78L567.43,336.57L566.91,338.87L567.08,339.84L565.09,340.51L563.01,342.21L561.38,344.46L560.72,346.75L560.25,349.77L559.08,351.52L559.03,351.58L565.55,354.4L577.39,358.16L581.64,360.42L584.68,363.43L586.13,366.31L585.37,369.2L583.52,370.26L579.45,370.95L574.88,372.83L572.17,376.22L570.4,380.67L568.44,383.05L571.93,391.75L574.96,395.25L579.68,398.76L589.24,403.27L592.25,405.9L592.87,410.03L592.59,413.09L591.97,415.79L591.2,418.26L591.97,420.54L592.72,423.64L591.75,427.3L591.03,431.27L594.24,432.05L600.28,430.39L605.63,430.05L609.36,430.42L619.23,427.92L623.16,431.05L624.19,434.52L626.8,437.05L630.36,443.18L630.86,447.01L631.47,446.58L639.77,445.8L641.99,446.4L645.4,438.18L656.39,427.05L659.34,422.48L658.89,419.04L659.95,415.32L661.77,410.22L658.01,408.53L652.3,405.28L649.14,403.02L644.78,402.27L639.92,399.36L635.91,396.26L635.46,392.85L639.09,387.74L640.56,381.67L639.32,375.96L639.04,372.02L636.36,369.95L632.94,368.26L630.89,364.68L628.22,361.93L625.21,362.18L622.21,361.61L619.74,359.17L616.47,356.79L611.32,355.15L608.61,352.24L609.5,347.63L606.68,344.37L601.31,342.61L598.09,341.08L594.7,340.1L590.77,338.57L587.64,331.75L584.01,329.31L573.31,330.07z" />
    

臺(tái)灣地圖的SVG文件是一個(gè)XML文件而芥,把他放到res/raw/taiwanhigh.xml律罢,在MainActivity中可以通過getResources().openRawResource(R.raw.taiwanhigh)獲取到該XML文件,進(jìn)行XML解析并通過工具類PathParser拿到里面的PathData棍丐,將路徑數(shù)據(jù)作為一個(gè)成員變量賦值給自定義的地圖控件TaiWan误辑。通過for循環(huán)遍歷整個(gè)XML把所有城市的數(shù)據(jù)獲取到。通過定義一個(gè)City類來城市的PathData歌逢。我們需要的不僅僅是Path數(shù)據(jù)巾钉,更需要一整塊PathData所圍繞封閉的Region(區(qū)域),才能知道手指是否點(diǎn)在這個(gè)城市上面秘案,這里要new一個(gè)Region砰苍,并使用path_svg.computeBounds(rectF, true)計(jì)算出PathData所占的區(qū)域潦匈。

private TaiWan taiWan;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        taiWan = (TaiWan) findViewById(R.id.taiwan);
        ParseSVG();
    }

    private void ParseSVG() {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            List<City> cities = new ArrayList<>();
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputStream is = getResources().openRawResource(R.raw.taiwanhigh);
            Document document = builder.parse(is);
            NodeList svgNodeList = document.getElementsByTagName("path");
            for (int i = 0; i < svgNodeList.getLength(); i++) {
                Element element = (Element) svgNodeList.item(i);
                String path = element.getAttribute("android:pathData");
                City city = new City(this);
                Path path_svg = PathParser.createPathFromPathData(path);
                city.setPath(path_svg);
                RectF rectF = new RectF();
                path_svg.computeBounds(rectF, true);
                Region region = new Region();
                region.setPath(path_svg, new Region((int) (rectF.left), (int) (rectF.top), (int) (rectF.right), (int) (rectF.bottom)));
                city.setRegion(region);
                cities.add(city);
            }
            taiWan.setCities(cities);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

顯而易見地是,我們自定義了Taiwan作為地圖控件赚导,并在Taiwan里有一個(gè)集合cities历等,cities里存放了所有城市City的PathData,在onDraw中只要繪制出PathData辟癌,就能夠顯示出整個(gè)臺(tái)灣地圖。為了使圖片好看一點(diǎn)荐捻,我把畫布在X黍少,Y坐標(biāo)上都擴(kuò)大了1.2倍。 接下來只要處理好手指點(diǎn)擊在地圖上的變色效果就好了处面。在Touch事件里面厂置,獲取觸摸點(diǎn)event.getX()和event.getY(),遍歷所有的City并使用region.contains((int) (x / 1.2f), (int) (y / 1.2f))判斷該點(diǎn)是否處在Region中魂角,如果在則表示手指按在該城市上面昵济,在City中定義了布爾值isTouch,當(dāng)手指按下時(shí)為True野揪,其他狀態(tài)為False访忿。之所以在 x 和 y都要 除以1.2f 是由于之前我把畫布在X,Y坐標(biāo)上都擴(kuò)大了1.2倍斯稳,現(xiàn)在需要調(diào)整坐標(biāo)系來要正確計(jì)算Region海铆。最后別忘記調(diào)用invalidate,通知系統(tǒng)重繪挣惰。

public class TaiWan extends View {

    List<City> cities;

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

    public TaiWan(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (null != cities && cities.size() > 0) {
            canvas.scale(1.2f, 1.2f);
            for (City city : cities) {
                city.draw(canvas);
            }
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int x = (int) event.getX();
                int y = (int) event.getY();
                for (City city : cities) {
                    Region region = city.getRegion();
                    boolean isContain = region.contains((int) (x / 1.2f), (int) (y / 1.2f));
                    if (isContain) {
                        city.setTouch(true);
                    } else {
                        city.setTouch(false);
                    }
                }
                invalidate();
        }
        return true;
    }


    public List<City> getCities() {
        return cities;

    }

    public void setCities(List<City> cities) {
        this.cities = cities;
    }
}

City的代碼如下卧斟,isTouch為True,按下時(shí)繪制實(shí)心Path并附加陰影憎茂,isTouch為False時(shí)繪制空心區(qū)域珍语,so easy!

public class City {

    private Context context;
    private Path path;
    private Paint paint;
    private boolean isTouch;
    private Region region;

    public City(Context context) {
        this.context = context;
        paint = new Paint();
        paint.setStrokeWidth(3);
        paint.setAntiAlias(true);
    }

    public boolean isTouch() {
        return isTouch;
    }

    public void setTouch(boolean touch) {
        isTouch = touch;
    }

    public Path getPath() {
        return path;
    }

    public void setPath(Path path) {
        this.path = path;
    }

    public void draw(Canvas canvas) {
        if (isTouch()) {
            int color = getRanColor();
            paint.setColor(color);
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            paint.setShadowLayer(8, 3, 3, Color.DKGRAY);

        } else {
            paint.setColor(this.context.getResources().getColor(R.color.color1));
            paint.setStyle(Paint.Style.STROKE);
        }
        canvas.drawPath(path, paint);
    }

    private int getRanColor() {
        int[] colors = {this.context.getResources().getColor(R.color.color2),
                this.context.getResources().getColor(R.color.color3), this.context.getResources().getColor(R.color.color4)};
        return colors[(int) (Math.random() * 3)];
    }

    public Region getRegion() {
        return region;
    }

    public void setRegion(Region region) {
        this.region = region;
    }
}

完整代碼可見 : https://github.com/pengzee/SVG_TW

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竖幔,一起剝皮案震驚了整個(gè)濱河市板乙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赏枚,老刑警劉巖亡驰,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異饿幅,居然都是意外死亡凡辱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門栗恩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來透乾,“玉大人,你說我怎么就攤上這事∪槲冢” “怎么了捧韵?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)汉操。 經(jīng)常有香客問我再来,道長(zhǎng),這世上最難降的妖魔是什么磷瘤? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任芒篷,我火速辦了婚禮,結(jié)果婚禮上采缚,老公的妹妹穿的比我還像新娘针炉。我一直安慰自己,他們只是感情好扳抽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布篡帕。 她就那樣靜靜地躺著,像睡著了一般贸呢。 火紅的嫁衣襯著肌膚如雪镰烧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天楞陷,我揣著相機(jī)與錄音拌滋,去河邊找鬼。 笑死猜谚,一個(gè)胖子當(dāng)著我的面吹牛败砂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魏铅,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昌犹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了览芳?” 一聲冷哼從身側(cè)響起斜姥,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沧竟,沒想到半個(gè)月后铸敏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悟泵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年杈笔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糕非。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒙具,死狀恐怖球榆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情禁筏,我是刑警寧澤持钉,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站篱昔,受9級(jí)特大地震影響每强,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜州刽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一舀射、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怀伦,春花似錦、人聲如沸山林。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)驼抹。三九已至桑孩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間框冀,已是汗流浹背流椒。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留明也,地道東北人宣虾。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像温数,于是被迫代替她去往敵國(guó)和親绣硝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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

  • 使用SVG打造一個(gè)可以交互的地圖 首先還是看看效果 交互感覺好像簡(jiǎn)單撑刺,但是這個(gè)地圖怎么繪制呢鹉胖? 思路:利用Xml解...
    laer_L閱讀 10,243評(píng)論 5 11
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件够傍、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評(píng)論 4 62
  • 這個(gè)社會(huì)是個(gè)爆炸的時(shí)代甫菠,非常多的信息,極其多的信息通過手機(jī)冕屯,電腦等媒介進(jìn)行傳播寂诱,其中不乏一些能滿足人們空虛的心靈,...
    neurall閱讀 441評(píng)論 0 1
  • 在未來新世界安聘,機(jī)器已經(jīng)替代了絕大多數(shù)工作刹衫。因此很多人都不能再去工作醋寝,而無所事事,政府為了避免這么多無所事事的人會(huì)產(chǎn)...
    老祝讀書閱讀 317評(píng)論 0 0