iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Mobile Development

Android 口罩地圖入門實戰 30 天 (使用 Kotlin 程式語言)系列 第 29

Day 29:Google Map 自訂資訊視窗

本篇文章同步發表在 HKT 線上教室 部落格,線上影音教學課程已上架至 UdemyYoutube 頻道。另外,想追蹤更多相關技術資訊,歡迎到 臉書粉絲專頁 按讚追蹤喔~

程式碼範例

範例名稱:Google Map 自訂資訊視窗
開發人員:HKT (侯光燦)
程式語言:Kotlin
開發環境:Android Studio 4.1.2 & Android 11 & Kotlin 1.4.30
授權範圍:使用時必須註明出處且不得為商業目的之使用
範例下載點:點我下載

昨天,我們使用了 Google Map 官方預設的資訊視窗,真的很簡潔、很方便,立刻可上手使用,但相對顯示上比較陽春。若我們想要顯示更多資訊內容或樣式,可以透過自定義資訊視窗來解決這個問題。

自定義資訊視窗 Wireframe

自定義資訊視窗佈局

在 layout 資料夾新增 info_window.xml,跟RecyleView 佈局一樣,但因為資訊視窗關係,因為沒有 parent 可以對齊寬度和高度,所以不能採用 match_parent ,需要設定一個大小或根據內容決定整體寬度和高度大小。所以佈局相似,但我們需修改一下相關設定。完整細節如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    app:cardCornerRadius="8dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/layout_item"
        android:layout_width="300dp"
        android:layout_height="150dp"
        android:background="?android:attr/selectableItemBackground"
        android:clickable="true"
        android:focusable="true"
        android:paddingBottom="20dp">


        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="15dp"
            android:text="藥局名稱"
            android:textColor="#424242"
            android:textSize="30dp"
            app:layout_constraintBottom_toTopOf="@+id/layout_adult"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/layout_adult"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_amount_info"
            android:padding="10dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/layout_child"
            app:layout_constraintTop_toBottomOf="@+id/tv_name">

            <TextView
                android:id="@+id/tv_adult"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="成人口罩"
                android:textColor="#ffffff"
                android:textSize="20dp"
                android:textStyle="bold"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/tv_adult_amount"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="口罩數量"
                android:textColor="#ffffff"
                android:textSize="16dp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tv_adult" />
        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/layout_child"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_amount_info"
            android:padding="10dp"
            app:layout_constraintLeft_toRightOf="@+id/layout_adult"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_name">

            <TextView
                android:id="@+id/tv_child"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="小孩口罩"
                android:textColor="#ffffff"
                android:textSize="20dp"
                android:textStyle="bold"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/tv_child_amount"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="口罩數量"
                android:textColor="#ffffff"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tv_child" />
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

自定義 InfoWindowAdapter

新增 MyInfoWindowAdapter.kt 覆寫實作 GoogleMap.InfoWindowAdapter 的方法:

package com.thishkt.pharmacydemo.adapter

import android.app.Activity
import android.content.Context
import android.view.View
import android.widget.TextView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.Marker
import com.thishkt.pharmacydemo.R

class MyInfoWindowAdapter(context: Context) : GoogleMap.InfoWindowAdapter {

    //指定自定義資訊視窗,顯示佈局的樣式
    var mWindow: View = (context as Activity).layoutInflater.inflate(R.layout.info_window, null)

    private fun render(marker: Marker, view: View) {

        val tvName = view.findViewById<TextView>(R.id.tv_name)
        val tvAdultAmount = view.findViewById<TextView>(R.id.tv_adult_amount)
        val tvChildAmount = view.findViewById<TextView>(R.id.tv_child_amount)

        //透過 marker.snippet 傳遞口罩數量,將資料拆解後,指定到對應的 UI 欄位上顯示
        val mask = marker.snippet.toString().split(",")

        //藥局名稱
        tvName.text = marker.title
        
        //成人口罩數量
        tvAdultAmount.text = mask[0]
        
        //小孩口罩數量
        tvChildAmount.text = mask[1]
    }

    override fun getInfoContents(marker: Marker): View {
        render(marker, mWindow)
        return mWindow
    }

    override fun getInfoWindow(marker: Marker): View? {
        return null
    }
}

設定 InfoWindowAdapter

最後設定我們的地圖 googleMap ,載入我們自定義的資訊視窗,MyInfoWindowAdapter

currLocationMarker?.remove()
googleMap?.setInfoWindowAdapter(MyInfoWindowAdapter(mContext))
currLocationMarker = googleMap?.addMarker(
    MarkerOptions()
        .position(currentLocation)
        .title("現在位置")
        .snippet("100,66")
)
currLocationMarker?.showInfoWindow()

輸出結果

資訊視窗點擊事件

加入 setOnInfoWindowClickListener 處理事件,這邊簡單示範,點擊資訊視窗會在 Log 視窗印出該間藥局名稱:

googleMap?.setOnInfoWindowClickListener {
    Log.d(
        "HKT",
        "title: ${currLocationMarker?.title}"
    )
}

參考資料

HKT 線上教室
https://tw-hkt.blogspot.com/

Freepik
https://www.freepik.com/


那今天【iThome 鐵人賽】就介紹到這邊囉~

順帶一提,KT 線上教室,臉書粉絲團,會不定期發佈相關資訊,不想錯過最新資訊,不要忘記來按讚,追蹤喔!也歡迎大家將這篇文章分享給更多人喔。

我們明天再見囉!!!掰掰~


上一篇
Day 28:Google Map 顯示目前位置
下一篇
Day 30:Google Map 結合口罩資料 & 鐵人賽最後一天
系列文
Android 口罩地圖入門實戰 30 天 (使用 Kotlin 程式語言)30

尚未有邦友留言

立即登入留言