高級 UI 成長之路 (七) SVG 基礎使用 + 繪製中國地圖

前言

前面陸陸續續寫了幾篇 高級 UI 系列文章 ,感受還不錯。由於工做內容緣由做者對 UI 開發涉及的不多,因此打算寫一點關於 UI 的文章,也算是給本身一個全面的複習。本篇文章仍是 基本概念 + 實戰來說解。php

概念

SVG 的全稱是 (Scalable Vector Graphics) 它是一個可縮放的矢量圖形,是專門用於網絡的矢量圖標準,與矢量圖相對應的是位圖,Bitmap 就是位圖,它由一個個像素點組成,當圖片放大到必定大小時, 就會出現馬賽克現象,Photoshop 就是經常使用的位圖處理軟件,而矢量圖則由一個個點組成,通過數學計算利用直線和曲線繪製而成,不管如何放大,都不會出現馬賽克問題,illustrator 就是經常使用的矢量圖繪圖軟件。java

SVG VS Bitmap

好處:android

  1. SVG 使用 XML 格式定義圖形,,可被很是用的多的工具讀取和修改;
  2. SVG 由點來存儲,由計算機根據點信息繪圖,不會失真,無須根據分辨率適配多套圖標;
  3. SVG 的佔用空間比 Bitmap 小,好比一張 500px * 500px 的圖像,轉成 SVG 後佔用的空間大小是 20KB, 而 PNG 圖片則須要 732KB 的空間。
  4. SVG 能夠轉換 Path 路徑,與 Path 動畫相結合,能夠造成更豐富的動畫。

vector 標籤

在 Android 中, SVG 矢量圖是使用標籤訂義的,並存放在 res/drawable/ 目錄下。一段簡單的 SVG 圖像代碼定義以下:git

<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:height="24dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="24dp" tools:ignore="MissingDefaultResource">
    <path android:fillColor="#040000" android:pathData="M513.29,738h-2.3V0h2.3z"/>
    <path android:fillColor="#040000" android:pathData="M512.08,727.97S482.38,896.04 480.09,939.08c-0.76,14.31 -9.58,84.92 32.88,84.92"/>
    <path android:fillColor="#040000" android:pathData="M511.02,1024c42.47,0 33.66,-70.6 32.89,-84.92 -2.3,-43.04 -31.99,-211.11 -31.99,-211.11"/>
</vector>
複製代碼

它定義的圖像以下所示:github

上面水滴形狀就是呈現出來的對應的圖像,在這段代碼中,首先使用 vector 標籤來指定這是一幅 SVG 圖像,而它有下面幾個屬性。canvas

  • width/height : 表示該 SVG 寬高
  • viewportHeight/viewportWidth: 表示 SVG 圖形劃分的比例

path 標籤

經常使用屬性網絡

標籤名稱 說明
android:name 聲明一個標記,相似於 ID ,便於對其作動畫的時候順利地找到該節點
android:pathData 對 SVG 矢量圖的描述
android:strokeWidth 畫筆的寬度
android:fillColor 填充顏色
android:fillAlpha 填充顏色的透明度
android:strokeColor 描邊顏色
android:strokeWidth 描邊寬度
android:strokeAlpha 描邊透明度
android:strokeLineJoin 用於指定折線拐角形狀,取值有 miter (結合處爲銳角)、round(結合處爲圓弧)、bevel(結合處爲直線)
android:strokeLineCap 畫出線條的終點的形狀(線帽),取值有 butt(無限帽) 、round (圓形線帽)、square(方形線帽)
android:strokeMiterLimit 設置斜角的上限

android:trimPathStart 屬性app

該屬性用於指定路徑從哪裏開始,取值 0 ~ 1,表示路徑開始位置的百分比。當取值爲 0 時,表示從頭部開始;當取值爲 1 時,整條路徑不可見。dom

android:trimPathEnd 屬性ide

該屬性用於指定路徑的結束位置,取值爲 0 ~ 1 ,表示路徑結束位置的百分比。當取值爲 1 時,路徑正常結束;當取值爲 0 時,表示從頭開始位置就已經結束了,整條路徑不可見。

android:trimPathOffset 屬性

該屬性用於指定結果路徑的位移距離,取值爲 0 ~ 1 。當取值爲 0 時,不進行位移;當取值爲 1 時,位移整條路徑的長度。

android:pathData 屬性

在 path 標籤中,主要經過 pathData 屬性來指定 SVG 圖像的顯示內容。而 pathData 屬性初 M 和 L 指令之外,還有更多的指定。

指令 對應 說明
M moveto(M x,y) 將畫筆移動到指定的地方
L lineto(L X,Y) 畫直線到指定的座標位置
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 Belaizer curveto(T ENDX,ENDY) 映射前面路徑後的終點
A elliptic Arc(A RX,RY,XROTATION,FLAYG1,FLAY2,X,Y) 弧線
Z Closepath 關閉路徑

製做 SVG 圖像

方法一: 設計軟件

若有你有繪圖基礎,則可使用 Illustrator 或在線 SVG 工具製做 SVG 圖像,好比:editor.method.ac/ ,或經過 SVG 源文件下載網站下載後進行編輯。

方法二: Iconfont

阿里巴巴的矢量圖庫

Android 中引入 SVG 圖像

準備工做

咱們知道在 Android 中是不支持直接使用 SVG 圖像解析的,咱們必須將 SVG圖像轉換爲 vector 標籤描述,這裏有 2 種方法;

方法一: 在線轉換

點擊跳轉在線轉換網站

方法二: AS 轉

按照我上面的步驟,就能夠生成 Vector 圖像了

基礎使用

下面對 ImageView 怎麼直接使用 vector 進行說明(ps:這裏用的 androidx 版本,若是是低版本須要本身去作兼容);

  1. 在 ImageView 中使用

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:ignore="MissingDefaultResource">
    
        <ImageView android:id="@+id/iv" android:layout_centerInParent="true" android:layout_width="match_parent" android:src="@drawable/ic_line" android:layout_height="500dp"/>
    
    </RelativeLayout>
    複製代碼

進階使用

前面講解了 vector 標籤,靜態顯示 vector 和製做 SVG 圖像的方法,那麼該小節就講解動態的 vector, 動態的 vector 所實現的效果纔是 SVG 圖像在 Android 應用中的精髓。

要實現 Vector 動畫,首先須要 Vector 圖像和它所對應的動畫,這裏依然使用上一小節水滴狀態的圖像,

先來看一下效果:

  1. 給 path 定義 name,以下所示

  2. 定義一個 Animator 文件,以表示對這幅 Vector 圖像作動畫

    <?xml version="1.0" encoding="utf-8"?>
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="trimPathStart" android:valueFrom="1" android:valueTo="0" android:duration="3000" >
    </objectAnimator>
    複製代碼

    須要注意的是,這裏的文件是對應 Vector 中 path 標籤的,這裏動畫效果是動態改變 path 標籤的 trimPathStart 屬性值,從 0 ~ 1 。

  3. 定義 animated-vector 進行關聯

    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                     xmlns:tools="http://schemas.android.com/tools" android:drawable="@drawable/ic_line"
                     tools:targetApi="lollipop">
    
        <target android:animation="@anim/anim_start"
                android:name="num_1"></target>
        <target android:animation="@anim/anim_start"
                android:name="num_2"></target>
    
        <target android:animation="@anim/anim_start"
                android:name="num_3"></target>
    </animated-vector>
    複製代碼

    在上述代碼中,drawable 表明關聯的 vector 圖像,target 表明將 path name 和動畫進行關聯

  4. 代碼中進行設置

    class SVGDemo1Activity : AppCompatActivity() {
    
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_svg)
            startAnimatabe()
        }
    
        private fun startAnimatabe() {
            val animatedVectorDrawable = AnimatedVectorDrawableCompat.create(this, R.drawable.line_animated_vector)
            iv.setImageDrawable(animatedVectorDrawable)
            val animatable = iv.drawable as Animatable
            animatable.start()
        }
    }
    複製代碼

實戰

輸入搜索動畫

  1. 利用在線繪製 SVG 圖標網站 製做搜索圖標

    能夠本身隨意搗鼓繪製,繪製好了以後點擊視圖->源代碼,將 SVG 代碼複製出來保存成 search_svg.xml

  2. 在線轉換 svg2vector

    點擊空白或者直接將 SVG 拖拽指定區域進行轉換

  3. 將轉換好的 Android 格式的 vector 導入 AS

  4. 開始製做動畫關聯

    //1.在 /res/aniamator 文件夾下 定義動畫
    <?xml version="1.0" encoding="utf-8"?>
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="trimPathStart" android:valueFrom="1" android:valueTo="0" android:duration="2000" >
    </objectAnimator>
    
    //2. 在/res/drawable/ 定義 vector
    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="580dp" android:height="400dp" android:viewportWidth="580" android:viewportHeight="400">
    
    
        <path android:name="svg_1" android:strokeColor="#000" android:strokeWidth="1.5" android:pathData="M 164.54545 211.91761 L 380 212.8267" />
        <path android:name="svg_2" android:strokeColor="#000" android:strokeWidth="1.5" android:pathData="M 360 180.09943 C 366.024924042 180.09943 370.90909 184.780091469 370.90909 190.55398 C 370.90909 196.327868531 366.024924042 201.00853 360 201.00853 C 353.975075958 201.00853 349.09091 196.327868531 349.09091 190.55398 C 349.09091 184.780091469 353.975075958 180.09943 360 180.09943 Z" />
        <path android:name="svg_3" android:strokeColor="#000" android:strokeWidth="1.5" android:pathData="M 369.09091 197.37216 L 380.90909 208.28125" />
    </vector>
    
    
    //3. 在/res/drawable/ 關聯動畫和 vector
    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:drawable="@drawable/search_svg" tools:targetApi="lollipop">
    
        <target android:animation="@animator/anim_start" android:name="svg_1"></target>
        <target android:animation="@animator/anim_start" android:name="svg_2"></target>
    
        <target android:animation="@animator/anim_start" android:name="svg_3"></target>
    </animated-vector>
    複製代碼
  5. 效果

    仍是很炫吧,😁!代碼在GitHub

警車燈閃爍

詳細代碼請移步GitHub

今日頭條下拉刷新動畫

來一個複雜組合動畫,請看下面效果圖:

  1. 準備 vector 數據

    <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="200dp" android:height="200dp" android:viewportHeight="200" android:viewportWidth="200">
    
        <path android:name="tt_1" android:fillColor="#C2BFBF" android:pathData=" M20,30 L100,30 M100,30 L100,90 M100,90 L20,90 M20,90 L20,30" android:strokeColor="#C2BFBF" android:strokeLineCap="round" android:strokeWidth="6"/>
        <path android:name="tt_2" android:pathData=" M120,30 L180,30 M120,60 L180,60 M120,90 L180,90" android:strokeColor="#C2BFBF" android:strokeLineCap="round" android:strokeWidth="6"/>
        <path android:name="tt_3" android:pathData=" M20,120 L180,120 M20,150 L180,150 M20,180 L180,180" android:strokeColor="#C2BFBF" android:strokeLineCap="round" android:strokeWidth="6"/>
    
        <path android:pathData=" M0,0 L200,0 M200,0 L200,200 M200,200 L0,200 M0,200 L0,0" android:strokeColor="#C2BFBF" android:strokeLineCap="round" android:strokeWidth="6"/>
    </vector>
    複製代碼
  2. 定義順時針執行動畫並作 pathData 變換

    這裏拿其中一個位置變化來舉例說明:

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially">//按順序執行
    
        //依次執行 pathData 位置變換
        <objectAnimator android:duration="600" android:interpolator="@android:interpolator/decelerate_cubic" android:propertyName="pathData" android:valueFrom=" M20,30 L100,30 M100,30 L100,90 M100,90 L20,90 M20,90 L20,30" android:valueTo=" M100,30 L180,30 M180,30 L180,90 M180,90 L100,90 M100,90 L100,30" android:valueType="pathType" />
        <objectAnimator android:duration="600" android:interpolator="@android:interpolator/decelerate_cubic" android:propertyName="pathData" android:valueFrom=" M100,30 L180,30 M180,30 L180,90 M180,90 L100,90 M100,90 L100,30" android:valueTo=" M100,120 L180,120 M180,120 L180,180 M180,180 L100,180 M100,180 L100,120" android:valueType="pathType" />
        <objectAnimator android:duration="600" android:interpolator="@android:interpolator/decelerate_cubic" android:propertyName="pathData" android:valueFrom=" M100,120 L180,120 M180,120 L180,180 M180,180 L100,180 M100,180 L100,120" android:valueTo=" M20,120 L100,120 M100,120 L100,180 M100,180 L20,180 M20,180 L20,120" android:valueType="pathType" />
        <objectAnimator android:duration="600" android:interpolator="@android:interpolator/decelerate_cubic" android:propertyName="pathData" android:valueFrom=" M20,120 L100,120 M100,120 L100,180 M100,180 L20,180 M20,180 L20,120" android:valueTo=" M20,30 L100,30 M100,30 L100,90 M100,90 L20,90 M20,90 L20,30" android:valueType="pathType" />
    </set>
    複製代碼

    若是對標籤中的定義還不瞭解的先去看下文章中 path 標籤 中的說明。若是不理解標籤意思,根本就看不懂。

  3. 進行關聯

    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:drawable="@drawable/ic_toutiao" tools:targetApi="lollipop">
    
    
        <target android:animation="@animator/tt_path_one" android:name="tt_1"/>
    
        <target android:animation="@animator/tt_path_two" android:name="tt_2"/>
    
        <target android:animation="@animator/tt_path_three" android:name="tt_3"/>
    
    
    </animated-vector>
    複製代碼
  4. 代碼控制重複執行

    class SVGDemo1Activity : AppCompatActivity() {
    
    
        var reStartTT = @SuppressLint("HandlerLeak")
        object : Handler() {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                startAnimatabe(R.drawable.line_animated_toutiao, true)
            }
        }
    
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_svg)
    
            //水滴動畫
            startWaterDropAnimator.setOnClickListener {
                startAnimatabe(R.drawable.line_animated_vector, false)
            }
            //搜索動畫
            startSearchAnimator.setOnClickListener {
                startAnimatabe(R.drawable.line_animated_search, false)
            }
            //執行警車動畫
            startPoliceCarAnimator.setOnClickListener {
                startAnimatabe(R.drawable.line_animated_car, false)
            }
            //執行頭條動畫
            startTTAnimator.setOnClickListener {
                startAnimatabe(R.drawable.line_animated_toutiao, true)
            }
        }
    
        private fun startAnimatabe(lineAnimatedVector: Int, isRegister: Boolean): Animatable {
            val animatedVectorDrawable = AnimatedVectorDrawableCompat.create(this, lineAnimatedVector)
            iv.setImageDrawable(animatedVectorDrawable)
            val animatable = iv.drawable as Animatable
            animatable.start()
            animatedVectorDrawable!!.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
                override fun onAnimationEnd(drawable: Drawable?) {
                    super.onAnimationEnd(drawable)
                    if (!isRegister) return
                    animatedVectorDrawable.unregisterAnimationCallback(this)
                    //從新開始在 xml 設置 restart 無效暫時用 Handler 實現了。
                    reStartTT.sendEmptyMessage(0)
    
                }
            })
            return animatable
    
        }
    }
    複製代碼

    詳細代碼請移步GitHub

繪製中國地圖

該篇以前實現 SVG pathData 都是利用 ImageView 來實現,並非全部的場合都適合上面的方式,好比我想要實現 pathData 區域點擊,那麼上面所講的方式應該是不能實現,下面咱們以一個實例來看怎麼自定義 View 實現 PathData 和 pathData 區域點擊事件。

下面咱們利用 path 來繪製一箇中國地圖,先來看一個最終效果圖,以下:

看起來是否是很炫,還不錯,嘿嘿,下面咱們就來看一下如何實現。

  1. 準備地圖 SVG

    • 首先去下載地圖數據

    • 選擇下載免費的地圖數據

    • 找到對應的國家點擊下載 svg 數據

    • 選擇對應的地圖數據,我這裏下載的是高質量的 SVG

  2. SVG to Vector xml

    將下載好的 china.svg 格式的文件轉爲 vector 節點的 xml 數據 或者用 AS 自帶轉也行,看我的愛好。

    轉好以後放入 AS 中,以下所示

    如今有了這些數據,咱們就能夠解析 xml path 節點,拿到 pathData 數據咱們不就能夠繪製 path 了嘛。下面就開始解析 xml ,解析的方法不少種,咱們這裏用 dom 解析。

  3. 開始解析 xml

    解析 xml 有不少種方式,這裏就直接使用 DOM 解析,pathData2Path 我這裏直接用 Android SDK 提供的 android.support.v4.graphics#PathParser 因爲源碼中它被標註了 hide 屬性 ,咱們須要直接將它 copy 到咱們本身項目中, 具體轉化請看以下代碼:

    /** * 開始解析 xml */
            public fun dom2xml(stream: InputStream?): MutableList<MapData> {
                mapDataLists.clear()
                //dom
                val newInstance = DocumentBuilderFactory.newInstance()
                val newDocumentBuilder = newInstance.newDocumentBuilder()
                //拿到 Docment 對象
                val document = newDocumentBuilder.parse(stream)
                //獲取 xml 中屬於 path 節點的全部信息
                val elementsByTagName = document.getElementsByTagName(PATH_TAG)
    
                //定義四個點,肯定整個 map 的範圍
                var left = -1f
                var right = -1f
                var top = -1f
                var bottom = -1f
                //開始遍歷標籤,拿到 path 數據組
                for (pathData in 0 until elementsByTagName.length) {
                    val item = elementsByTagName.item(pathData) as Element
                    val name = item.getAttribute("android:name")
                    val fillColor = item.getAttribute("android:fillColor")
                    val strokeColor = item.getAttribute("android:strokeColor")
                    val strokeWidth = item.getAttribute("android:strokeWidth")
                    val pathData = item.getAttribute("android:pathData")
                    val path = PathParser.createPathFromPathData(pathData)
                    mapDataLists.add(MapData(name, fillColor, strokeColor, strokeWidth, path))
                    //獲取控件的寬高
                    val rect = RectF()
                    //獲取到每一個省份的邊界
                    path.computeBounds(rect, true)
                    //遍歷取出每一個path中的left取全部的最小值
                    left = if (left == -1f) rect.left else Math.min(left, rect.left)
                    //遍歷取出每一個path中的right取全部的最大值
                    right = if (right == -1f) rect.right else Math.max(right, rect.right)
                    //遍歷取出每一個path中的top取全部的最小值
                    top = if (top == -1f) rect.top else Math.min(top, rect.top)
                    //遍歷取出每一個path中的bottom取全部的最大值
                    bottom = if (bottom == -1f) rect.bottom else Math.max(bottom, rect.bottom)
                }
                //MAP 的矩形區域
                MAP_RECTF = RectF(left, top, right, bottom)
                return mapDataLists;
            }
    複製代碼
  4. 進行控件測量適配橫豎屏切換和寬高定義 wrap_content 模式

    /** * 開始測量 */
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            //測量模式
            var widthMode = MeasureSpec.getMode(widthMeasureSpec)
            var heightMode = MeasureSpec.getMode(heightMeasureSpec)
            //測量大小
            widthSize = MeasureSpec.getSize(widthMeasureSpec)
            heightSize = MeasureSpec.getSize(heightMeasureSpec)
    
            if (!MAP_RECTF.isEmpty && mMapRectHeight != 0f && mMapRectWidth != 0f) {
                //顯示比例
                scaleHeightValues = heightSize / mMapRectHeight
                scaleWidthValues = widthSize / mMapRectWidth
            }
    
            //xml 文件中寬高 wrap_content
            if (widthMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.AT_MOST) {
                //若是是橫屏寬保留最大,高須要適配
                if (widthSize < heightSize && mMapRectHeight != 0f) {
                    setMeasuredDimension(widthSize, (mMapRectHeight * scaleWidthValues).toInt())
                } else {
                    setMeasuredDimension(widthSize, heightSize)
                }
            } else {
                setMeasuredDimension(widthSize, heightSize)
            }
        }
    複製代碼
  5. 開始繪製 path

    /** * 繪製 Map 數據 */
        @SuppressLint("Range")
        private fun drawMap(canvas: Canvas) {
            canvas.save()
            if (widthSize > heightSize) {
                canvas.scale(scaleWidthValues, scaleHeightValues)
            } else {
                canvas.scale(scaleWidthValues, scaleWidthValues)
            }
    
            mapDataList.forEach { data ->
                run {
                    if (data.isSelect) {
                        drawPath(data, canvas, Color.RED)
                    } else {
                        drawPath(data, canvas, Color.parseColor(data.fillColor))
                    }
                }
            }
            canvas.restore()
            canvas.drawText("中國🇨🇳地圖", widthSize / 2 - mPaintTextTitle.measureText("中國🇨🇳地圖") / 2f, 100f, mPaintTextTitle)
        }
    
        /** * 開始繪製 Path */
        private fun drawPath( data: MapData, canvas: Canvas, magenta: Int ) {
            mPaintPath.setColor(magenta)
            mPaintPath.setStyle(Paint.Style.FILL)
            mPaintPath.setTextSize(30f)
            mPaintPath.setStrokeWidth(data.strokeWidth.toFloat())
            canvas.drawPath(data.pathData, mPaintPath)
            val rectF = RectF()
            data.pathData.computeBounds(rectF, true)
            canvas.drawText(
                if (data.name.isEmpty()) "" else data.name,
                rectF.centerX() - mPaintText.measureText(data.name) / 2,
                rectF.centerY(), mPaintText
            )
        }
    複製代碼
  6. 給地圖添加各自的點擊事件

    override fun onTouchEvent(event: MotionEvent): Boolean {
            when (event.action) {
                MotionEvent.ACTION_DOWN -> return true
                MotionEvent.ACTION_UP -> {
                    handlerTouch(event.getX(), event.getY())
                }
            }
            return super.onTouchEvent(event)
        }
    
        /** * 處理點擊事件 */
        private fun handlerTouch(x: Float, y: Float) {
            if (mapDataList.size == 0) return
    
            var xScale = 0f
            var yScale = 0f
    
            if (widthSize > heightSize) {
                xScale = scaleWidthValues
                yScale = scaleHeightValues
            } else {
                xScale = scaleWidthValues
                yScale = scaleWidthValues
            }
            mapDataList.forEach { data ->
                run {
                    data.isSelect = false
                    if (isTouchRegion(x / xScale, y / yScale, data.pathData)) {
                        data.isSelect = true
                        postInvalidate()
                    }
                }
            }
        }
    }
    
    /** * 判斷是否在點擊區域內 */
    fun isTouchRegion(x: Float, y: Float, path: Path): Boolean {
        //建立一個矩形
        val rectF = RectF()
        //獲取到當前省份的矩形邊界
        path.computeBounds(rectF, true)
        //建立一個區域對象
        val region = Region()
        //將path對象放入到Region區域對象中
        region.setPath(path, Region(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt()))
        //返回是否這個區域包含傳進來的座標
        return region.contains(x.toInt(), y.toInt())
    }
    複製代碼

    詳細代碼請看MapView.kt

到這裏 SVG 知識已經講解完了,以爲還不過癮的能夠本身嘗試一下其餘國家的地圖繪製。

總結

這裏必定要注意在低版本上使用 SVG 存在兼容問題,須要各自查閱資料解決。

不知道還有沒有記得上一篇 高級 UI 成長之路 (六) PathMeasure 製做路徑動畫 中我提到了只要給我一個 Path 數據,我就能繪製出圖形,看完該篇是否是認爲說的沒毛病吧。建議你們在項目上多使用 SVG ,好處文章開頭也提到了,這裏就不在囉嗦了。到這裏 SVG 製做圖像和動畫效果就所有講完了。

工具

相關文章
相關標籤/搜索