国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android高級UI進(jìn)階之路(七)——SVG基礎(chǔ)使用(繪制中國地圖)

這篇具有很好參考價(jià)值的文章主要介紹了Android高級UI進(jìn)階之路(七)——SVG基礎(chǔ)使用(繪制中國地圖)。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

系列文章

Android高級UI進(jìn)階之路(一) —— View的基礎(chǔ)知識

Android高級UI進(jìn)階之路(二) —— 深入理解Android8.0 View的觸摸事件分發(fā)機(jī)制

Android高級UI進(jìn)階之路(三) —— 理解View的工作原理及自定義View入門

Android高級UI進(jìn)階之路(四) —— Paint渲染濾鏡xfermode使用

Android高級UI進(jìn)階之路(五) —— Canvas詳解

Android高級UI進(jìn)階之路(六) —— PathMeasure-制作路徑動(dòng)畫

Android高級UI進(jìn)階之路(七) —— SVG基礎(chǔ)使用(繪制中國地圖)

前言

前面陸陸續(xù)續(xù)寫了幾篇 高級 UI 系列文章 ,感覺還不錯(cuò)。因?yàn)楣ぷ鲀?nèi)容原因作者對 UI 開發(fā)涉及的很少,所以打算寫一點(diǎn)關(guān)于 UI 的文章,也算是給自己一個(gè)全面的復(fù)習(xí)。本篇文章還是 基本概念 + 實(shí)戰(zhàn)來講解。

概念

SVG 的全稱是 (Scalable Vector Graphics) 它是一個(gè)可縮放的矢量圖形,是專門用于網(wǎng)絡(luò)的矢量圖標(biāo)準(zhǔn),與矢量圖相對應(yīng)的是位圖,Bitmap 就是位圖,它由一個(gè)個(gè)像素點(diǎn)組成,當(dāng)圖片放大到一定大小時(shí), 就會(huì)出現(xiàn)馬賽克現(xiàn)象,Photoshop 就是常用的位圖處理軟件,而矢量圖則由一個(gè)個(gè)點(diǎn)組成,經(jīng)過數(shù)學(xué)計(jì)算利用直線和曲線繪制而成,無論如何放大,都不會(huì)出現(xiàn)馬賽克問題,illustrator 就是常用的矢量圖繪圖軟件。

SVG VS Bitmap

好處:

  1. SVG 使用 XML 格式定義圖形,,可被非常用的多的工具讀取和修改;

  1. SVG 由點(diǎn)來存儲(chǔ),由計(jì)算機(jī)根據(jù)點(diǎn)信息繪圖,不會(huì)失真,無須根據(jù)分辨率適配多套圖標(biāo);

  1. SVG 的占用空間比 Bitmap 小,比如一張 500px * 500px 的圖像,轉(zhuǎn)成 SVG 后占用的空間大小是 20KB, 而 PNG 圖片則需要 732KB 的空間。

  1. SVG 可以轉(zhuǎn)換 Path 路徑,與 Path 動(dòng)畫相結(jié)合,可以形成更豐富的動(dòng)畫。

vector 標(biāo)簽

在 Android 中, SVG 矢量圖是使用標(biāo)簽定義的,并存放在 res/drawable/ 目錄下。一段簡單的 SVG 圖像代碼定義如下:

<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>

它定義的圖像如下所示:

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

上面水滴形狀就是呈現(xiàn)出來的對應(yīng)的圖像,在這段代碼中,首先使用 vector 標(biāo)簽來指定這是一幅 SVG 圖像,而它有下面幾個(gè)屬性。

  • width/height : 表示該 SVG 寬高

  • viewportHeight/viewportWidth: 表示 SVG 圖形劃分的比例

path 標(biāo)簽

常用屬性

標(biāo)簽名稱

說明

android:name

聲明一個(gè)標(biāo)記,類似于 ID ,便于對其做動(dòng)畫的時(shí)候順利地找到該節(jié)點(diǎn)

android:pathData

對 SVG 矢量圖的描述

android:strokeWidth

畫筆的寬度

android:fillColor

填充顏色

android:fillAlpha

填充顏色的透明度

android:strokeColor

描邊顏色

android:strokeWidth

描邊寬度

android:strokeAlpha

描邊透明度

android:strokeLineJoin

用于指定折線拐角形狀,取值有 miter (結(jié)合處為銳角)、round(結(jié)合處為圓弧)、bevel(結(jié)合處為直線)

android:strokeLineCap

畫出線條的終點(diǎn)的形狀(線帽),取值有 butt(無限帽) 、round (圓形線帽)、square(方形線帽)

android:strokeMiterLimit

設(shè)置斜角的上限

android:trimPathStart 屬性

該屬性用于指定路徑從哪里開始,取值 0 ~ 1,表示路徑開始位置的百分比。當(dāng)取值為 0 時(shí),表示從頭部開始;當(dāng)取值為 1 時(shí),整條路徑不可見。

android:trimPathEnd 屬性

該屬性用于指定路徑的結(jié)束位置,取值為 0 ~ 1 ,表示路徑結(jié)束位置的百分比。當(dāng)取值為 1 時(shí),路徑正常結(jié)束;當(dāng)取值為 0 時(shí),表示從頭開始位置就已經(jīng)結(jié)束了,整條路徑不可見。

android:trimPathOffset 屬性

該屬性用于指定結(jié)果路徑的位移距離,取值為 0 ~ 1 。當(dāng)取值為 0 時(shí),不進(jìn)行位移;當(dāng)取值為 1 時(shí),位移整條路徑的長度。

android:pathData 屬性

在 path 標(biāo)簽中,主要通過 pathData 屬性來指定 SVG 圖像的顯示內(nèi)容。而 pathData 屬性初 M 和 L 指令以外,還有更多的指定。

指令

對應(yīng)

說明

M

moveto(M x,y)

將畫筆移動(dòng)到指定的地方

L

lineto(L X,Y)

畫直線到指定的坐標(biāo)位置

H

Horizontal lineto(H X)

畫水平線到指定的 X 坐標(biāo)位置

V

Vertical lineto(V Y)

畫垂直線到指定的 Y 坐標(biāo)位置

C

curveto(C X1,Y1,X2,Y2,ENDX,ENDY)

三階貝濟(jì)埃曲線

S

Smooth curveto(S X2,Y2,ENDX,ENDY)

三階貝濟(jì)埃曲線

Q

Quadratic Belzier curve(Q X,Y,ENDX,ENDY)

二階貝濟(jì)埃曲線

T

smooth quadratic Belaizer curveto(T ENDX,ENDY)

映射前面路徑后的終點(diǎn)

A

elliptic Arc(A RX,RY,XROTATION,FLAYG1,FLAY2,X,Y)

弧線

Z

Closepath

關(guān)閉路徑

制作 SVG 圖像

方法一: 設(shè)計(jì)軟件

如有你有繪圖基礎(chǔ),則可以使用 Illustrator 或在線 SVG 工具制作 SVG 圖像,比如:editor.method.ac/ ,或通過 SVG 源文件下載網(wǎng)站下載后進(jìn)行編輯。

方法二: Iconfont

阿里巴巴的矢量圖庫

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

Android 中引入 SVG 圖像

準(zhǔn)備工作

我們知道在 Android 中是不支持直接使用 SVG 圖像解析的,我們必須將 SVG圖像轉(zhuǎn)換為 vector 標(biāo)簽描述,這里有 2 種方法;

方法一: 在線轉(zhuǎn)換

點(diǎn)擊跳轉(zhuǎn)在線轉(zhuǎn)換網(wǎng)站

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

方法二: AS 轉(zhuǎn)

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

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

基礎(chǔ)使用

下面對 ImageView 怎么直接使用 vector 進(jìn)行說明(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>
android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

進(jìn)階使用

前面講解了 vector 標(biāo)簽,靜態(tài)顯示 vector 和制作 SVG 圖像的方法,那么該小節(jié)就講解動(dòng)態(tài)的 vector, 動(dòng)態(tài)的 vector 所實(shí)現(xiàn)的效果才是 SVG 圖像在 Android 應(yīng)用中的精髓。

要實(shí)現(xiàn) Vector 動(dòng)畫,首先需要 Vector 圖像和它所對應(yīng)的動(dòng)畫,這里依然使用上一小節(jié)水滴狀態(tài)的圖像,

先來看一下效果:

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔
  1. 給 path 定義 name,如下所示

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔
  1. 定義一個(gè) Animator 文件,以表示對這幅 Vector 圖像做動(dòng)畫

<?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>

需要注意的是,這里的文件是對應(yīng) Vector 中 path 標(biāo)簽的,這里動(dòng)畫效果是動(dòng)態(tài)改變 path 標(biāo)簽的 trimPathStart 屬性值,從 0 ~ 1 。

  1. 定義 animated-vector 進(jìn)行關(guān)聯(lián)

<?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 代表關(guān)聯(lián)的 vector 圖像,target 代表將 path name 和動(dòng)畫進(jìn)行關(guān)聯(lián)

  1. 代碼中進(jìn)行設(shè)置

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()
    }
}

實(shí)戰(zhàn)

輸入搜索動(dòng)畫

  1. 利用在線繪制 SVG 圖標(biāo)網(wǎng)站 制作搜索圖標(biāo)

可以自己隨意搗鼓繪制,繪制好了之后點(diǎn)擊視圖->源代碼,將 SVG 代碼復(fù)制出來保存成 search_svg.xml

  1. 在線轉(zhuǎn)換 svg2vector

點(diǎn)擊空白或者直接將 SVG 拖拽指定區(qū)域進(jìn)行轉(zhuǎn)換

  1. 將轉(zhuǎn)換好的 Android 格式的 vector 導(dǎo)入 AS

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔
  1. 開始制作動(dòng)畫關(guān)聯(lián)

//1.在 /res/aniamator 文件夾下 定義動(dòng)畫
<?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/ 關(guān)聯(lián)動(dòng)畫和 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>
  1. 效果

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

警車燈閃爍

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

今日頭條下拉刷新動(dòng)畫

來一個(gè)復(fù)雜組合動(dòng)畫,請看下面效果圖:

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔
  1. 準(zhǔn)備 vector 數(shù)據(jù)

<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>
  1. 定義順時(shí)針執(zhí)行動(dòng)畫并做 pathData 變換

這里拿其中一個(gè)位置變化來舉例說明:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"

     android:ordering="sequentially">//按順序執(zhí)行

    //依次執(zhí)行 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>

如果對標(biāo)簽中的定義還不了解的先去看下文章中 path 標(biāo)簽 中的說明。如果不理解標(biāo)簽意思,根本就看不懂。

  1. 進(jìn)行關(guān)聯(lián)

<?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>
  1. 代碼控制重復(fù)執(zhí)行

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)

        //水滴動(dòng)畫
        startWaterDropAnimator.setOnClickListener {
            startAnimatabe(R.drawable.line_animated_vector, false)
        }
        //搜索動(dòng)畫
        startSearchAnimator.setOnClickListener {
            startAnimatabe(R.drawable.line_animated_search, false)
        }
        //執(zhí)行警車動(dòng)畫
        startPoliceCarAnimator.setOnClickListener {
            startAnimatabe(R.drawable.line_animated_car, false)
        }
        //執(zhí)行頭條動(dòng)畫
        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 設(shè)置 restart 無效暫時(shí)用 Handler 實(shí)現(xiàn)了。
                reStartTT.sendEmptyMessage(0)

            }
        })
        return animatable

    }
}

繪制中國地圖

該篇之前實(shí)現(xiàn) SVG pathData 都是利用 ImageView 來實(shí)現(xiàn),并不是所有的場合都適合上面的方式,比如我想要實(shí)現(xiàn) pathData 區(qū)域點(diǎn)擊,那么上面所講的方式應(yīng)該是不能實(shí)現(xiàn),下面我們以一個(gè)實(shí)例來看怎么自定義 View 實(shí)現(xiàn) PathData 和 pathData 區(qū)域點(diǎn)擊事件。

下面我們利用 path 來繪制一個(gè)中國地圖,先來看一個(gè)最終效果圖,如下:

看起來是不是很炫,還不錯(cuò),嘿嘿,下面我們就來看一下如何實(shí)現(xiàn)。

  1. 準(zhǔn)備地圖 SVG

  • 首先去下載地圖數(shù)據(jù)

  • 選擇下載免費(fèi)的地圖數(shù)據(jù)

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

* 找到對應(yīng)的國家點(diǎn)擊下載 svg 數(shù)據(jù)

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

* 選擇對應(yīng)的地圖數(shù)據(jù),我這里下載的是高質(zhì)量的 SVG

  1. SVG to Vector xml

將下載好的 china.svg 格式的文件轉(zhuǎn)為 vector 節(jié)點(diǎn)的 xml 數(shù)據(jù) 或者用 AS 自帶轉(zhuǎn)也行,看個(gè)人愛好。

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

轉(zhuǎn)好之后放入 AS 中,如下所示

android svg,Android,android,SVG,vector,path,UI,Powered by 金山文檔

現(xiàn)在有了這些數(shù)據(jù),我們就可以解析 xml path 節(jié)點(diǎn),拿到 pathData 數(shù)據(jù)我們不就可以繪制 path 了嘛。下面就開始解析 xml ,解析的方法很多種,我們這里用 dom 解析。

  1. 開始解析 xml

解析 xml 有很多種方式,這里就直接使用 DOM 解析,pathData2Path 我這里直接用 Android SDK 提供的 android.support.v4.graphics#PathParser 由于源碼中它被標(biāo)注了 hide 屬性 ,我們需要直接將它 copy 到我們自己項(xiàng)目中, 具體轉(zhuǎn)化請看如下代碼:

        /**
         * 開始解析 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 節(jié)點(diǎn)的所有信息
            val elementsByTagName = document.getElementsByTagName(PATH_TAG)

            //定義四個(gè)點(diǎn),確定整個(gè) map 的范圍
            var left = -1f
            var right = -1f
            var top = -1f
            var bottom = -1f
            //開始遍歷標(biāo)簽,拿到 path 數(shù)據(jù)組
            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()
                //獲取到每個(gè)省份的邊界
                path.computeBounds(rect, true)
                //遍歷取出每個(gè)path中的left取所有的最小值
                left = if (left == -1f) rect.left else Math.min(left, rect.left)
                //遍歷取出每個(gè)path中的right取所有的最大值
                right = if (right == -1f) rect.right else Math.max(right, rect.right)
                //遍歷取出每個(gè)path中的top取所有的最小值
                top = if (top == -1f) rect.top else Math.min(top, rect.top)
                //遍歷取出每個(gè)path中的bottom取所有的最大值
                bottom = if (bottom == -1f) rect.bottom else Math.max(bottom, rect.bottom)
            }
            //MAP 的矩形區(qū)域
            MAP_RECTF = RectF(left, top, right, bottom)
            return mapDataLists;
        }
  1. 進(jìn)行控件測量適配橫豎屏切換和寬高定義 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)
        }
    }
  1. 開始繪制 path

    /**
     * 繪制 Map 數(shù)據(jù)
     */
    @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
        )
    }
  1. 給地圖添加各自的點(diǎn)擊事件

    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)
    }

    /**
     * 處理點(diǎn)擊事件
     */
    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()
                }
            }
        }
    }
}

/**
 * 判斷是否在點(diǎn)擊區(qū)域內(nèi)
 */
fun isTouchRegion(x: Float, y: Float, path: Path): Boolean {
    //創(chuàng)建一個(gè)矩形
    val rectF = RectF()
    //獲取到當(dāng)前省份的矩形邊界
    path.computeBounds(rectF, true)
    //創(chuàng)建一個(gè)區(qū)域?qū)ο?    val region = Region()
    //將path對象放入到Region區(qū)域?qū)ο笾?    region.setPath(path, Region(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt()))
    //返回是否這個(gè)區(qū)域包含傳進(jìn)來的坐標(biāo)
    return region.contains(x.toInt(), y.toInt())
}

到這里 SVG 知識已經(jīng)講解完了,覺得還不過癮的可以自己嘗試一下其他國家的地圖繪制。

總結(jié)

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

不知道還有沒有記得上一篇 [高級 UI 成長之路 (六) PathMeasure 制作路徑動(dòng)畫] 中我提到了只要給我一個(gè) Path 數(shù)據(jù),我就能繪制出圖形,看完該篇是不是認(rèn)為說的沒毛病吧。建議大家在項(xiàng)目上多使用 SVG ,好處文章開頭也提到了,這里就不在啰嗦了。到這里 SVG 制作圖像和動(dòng)畫效果就全部講完了。文章來源地址http://www.zghlxwxcb.cn/news/detail-753729.html

到了這里,關(guān)于Android高級UI進(jìn)階之路(七)——SVG基礎(chǔ)使用(繪制中國地圖)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Android-高級-UI-進(jìn)階之路-(五)-看完該篇文章-Canvas-你應(yīng)該會(huì)了

    Android-高級-UI-進(jìn)階之路-(五)-看完該篇文章-Canvas-你應(yīng)該會(huì)了

    /** 1. 繪制橢圓 */ canvas.drawOval(RectF(100f,500f,600f,800f),mPaint) /** 2. 繪制圓 */ mPaint.setColor(Color.YELLOW) mPaint.alpha = 100 canvas.drawCircle(400f,400f,200f,mPaint) 繪制 Bitmap // val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.gild_3) //第二個(gè),第三個(gè)參數(shù)代表起點(diǎn)位置 canvas.drawBitmap(bitmap,100f,100

    2024年03月28日
    瀏覽(11)
  • 靈魂畫師,Android繪制流程——Android高級UI(1)

    靈魂畫師,Android繪制流程——Android高級UI(1)

    繪制流程從何而起 Activity 的界面結(jié)構(gòu)在哪里開始形成 繪制流程如何運(yùn)轉(zhuǎn)起來 接下來我們就一個(gè)個(gè)目標(biāo)來 conquer。 我們一說到 繪制流程 ,就會(huì)想到或是聽過 onMeasure 、 onLayout 、 onDraw 這三個(gè)方法,但是有沒想過為什么我們開啟一個(gè)App或是點(diǎn)開一個(gè)Activity,就會(huì)觸發(fā)這一系列流

    2024年04月17日
    瀏覽(31)
  • Android架構(gòu)進(jìn)階之高級UI系列(精編解析,值得收藏)

    Android架構(gòu)進(jìn)階之高級UI系列(精編解析,值得收藏)

    public FrameHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: // 執(zhí)行doFrame // 如果啟用VSYNC機(jī)制,當(dāng)VSYNC信號到來時(shí)觸發(fā) doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: // 申請VSYNC信號,例如當(dāng)前需要繪制任務(wù)時(shí) doScheduleVsync()

    2024年04月14日
    瀏覽(28)
  • Android架構(gòu)進(jìn)階之高級UI系列(精編解析,值得收藏),Android開發(fā)面試技能介紹

    Android架構(gòu)進(jìn)階之高級UI系列(精編解析,值得收藏),Android開發(fā)面試技能介紹

    CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); // 根據(jù)指定的類型CallbackkQueue中查找到達(dá)執(zhí)行時(shí)間的CallbackRecord callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; if (callbackType == Choreograph

    2024年04月13日
    瀏覽(21)
  • Android之 SVG繪制

    Android之 SVG繪制

    一 SVG介紹 1.1?SVG(Scalable Vector Graphics)是可縮放矢量圖形的縮寫,它是一種圖形格式,其中形狀在XML中指定, 而XML又由SVG查看器呈現(xiàn)。 1.2 SVG可以區(qū)別于位圖,放大可以做到不模糊,可以做一些圖標(biāo),按鈕等繪制,但太復(fù)雜的話會(huì)導(dǎo)致渲染速度慢,占用內(nèi)存大。適合簡單的圖

    2024年02月09日
    瀏覽(15)
  • Canvas中的裁剪師講解與實(shí)戰(zhàn)——Android高級UI(1),Android體系化進(jìn)階學(xué)習(xí)圖譜

    Canvas中的裁剪師講解與實(shí)戰(zhàn)——Android高級UI(1),Android體系化進(jìn)階學(xué)習(xí)圖譜

    從今天開始我們聊一聊 Canvas 的API,因?yàn)镃anvas的API較多,所以我們分為幾次分享,首先分享的是裁剪類型的API使用。話不多說,先上實(shí)戰(zhàn)圖。 老夫的少女心 源碼地址文末會(huì)給出,了解原理才能更好地駕馭。 分享前,我們先來聊聊,在我們生活中如何繪制一張如下的圖。 我們

    2024年04月13日
    瀏覽(41)
  • Android基礎(chǔ)到進(jìn)階UI祖父級 ViewGroup介紹+實(shí)用

    Android基礎(chǔ)到進(jìn)階UI祖父級 ViewGroup介紹+實(shí)用

    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 如果是warp_content情況下,記錄寬和高 int width = 0; int height = 0; //記錄每一行的寬度,width不斷取最大寬度 int lineWidth = 0; //每一行的高度,累加至h

    2024年04月16日
    瀏覽(19)
  • 【Android Framework系列】第13章 SVG矢量圖形自定義組件(繪制中國地圖)

    【Android Framework系列】第13章 SVG矢量圖形自定義組件(繪制中國地圖)

    本章節(jié)我們來了解下什么是 SVG 矢量圖形,怎么通過 SVG 實(shí)現(xiàn)圖形的繪制,通過 SVG 實(shí)現(xiàn)不規(guī)則的自定義控件,項(xiàng)目實(shí)現(xiàn)一個(gè)中國地圖,實(shí)現(xiàn)每個(gè)省都能夠點(diǎn)擊,項(xiàng)目地址在文末請自取。 SVG 指可伸縮矢量圖形 (Scalable Vector Graphics) SVG 用來定義用于網(wǎng)絡(luò)的基于矢量的圖形 SVG 使用

    2024年02月10日
    瀏覽(25)
  • 使用svg在元素直接繪制連線箭頭

    使用svg在元素直接繪制連線箭頭

    注意:svg的圖形繪制的點(diǎn)位置坐標(biāo)是基于畫布的位置坐標(biāo),相當(dāng)于從左上角的點(diǎn)為起點(diǎn)。 先來個(gè)簡單示例: 上面示例中可以看到, svg 畫布的位置在哪, path 中點(diǎn)的坐標(biāo)就從哪里開始,默認(rèn)是從瀏覽器可視窗口的左上角開始。那么我們只要知道點(diǎn)的坐標(biāo)就能繪制箭頭了。 接

    2024年02月04日
    瀏覽(19)
  • CSS 使用 SVG 繪制動(dòng)態(tài)皮筋與小球交互動(dòng)畫

    CSS 使用 SVG 繪制動(dòng)態(tài)皮筋與小球交互動(dòng)畫

    使用 animation 控制 SVG 的 path 屬性執(zhí)行動(dòng)畫 使用 CSS 設(shè)置 SVG 部分屬性 實(shí)現(xiàn)上述代碼后,頁面效果如下: 完成上述代碼后,皮筋就可以開始運(yùn)動(dòng),效果圖如下: 完成上述代碼后就可以完成所有效果。 完整代碼下載

    2024年04月12日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包