為應用程式建立自訂「快速設定」方塊

快速設定是顯示在「快速設定面板」中的圖塊,代表使用者可以輕觸的操作,以便快速完成週期性工作。應用程式可以透過 TileService 類別為使用者提供自訂資訊方塊,並使用 Tile 物件追蹤資訊方塊的狀態。舉例來說,您可以建立一個資訊方塊,讓使用者開啟或關閉應用程式提供的 VPN。

「快速設定」面板,其中 VPN 圖塊已開啟和關閉
圖 1. 「快速設定」面板,其中 VPN 設定方塊已開啟和關閉。

決定何時建立圖塊

建議您為預期使用者會經常存取或需要快速存取 (或兩者皆是) 的特定功能建立資訊方塊。最有效的資訊方塊是同時符合這兩項特質的資訊方塊,可讓使用者快速存取經常執行的動作。

舉例來說,您可以為運動應用程式建立資訊方塊,讓使用者快速開始健身課程。不過,我們不建議為同一個應用程式建立資訊方塊,讓使用者查看完整的健身記錄。

運動應用程式資訊方塊用途
圖 2. 運動應用程式的建議和不建議資訊方塊範例。

為提升資訊方塊的曝光度和易用性,建議您避免下列做法:

  • 避免使用資訊方塊啟動應用程式。請改用應用程式捷徑或標準啟動器。

  • 避免將資訊方塊用於一次性使用者動作。請改用應用程式捷徑或通知。

  • 避免建立過多資訊方塊。建議每個應用程式最多使用兩個。請改用應用程式捷徑。

  • 避免使用只顯示資訊,但無法與使用者互動的資訊方塊。請改用通知或小工具。

建立資訊方塊

如要建立資訊方塊,您必須先建立適當的資訊方塊圖示,然後在應用程式的資訊清單檔案中建立並宣告 TileService。

快速設定範例提供建立及管理圖塊的範例。

建立自訂圖示

您必須提供自訂圖示,該圖示會顯示在「快速設定」面板的圖塊上。(您會在宣告 TileService 時新增這個圖示,詳情請見下一節)。圖示必須為純白色,背景透明,尺寸為 24 x 24dp,並採用 VectorDrawable 格式。

向量可繪項目範例
圖 3. 向量可繪項目範例。

建立圖示,以視覺方式提示資訊方塊的用途。這有助於使用者判斷資訊方塊是否符合需求。舉例來說,您可以為運動應用程式的資訊方塊建立碼錶圖示,讓使用者開始運動工作階段。

建立及宣告 TileService

為資訊方塊建立服務,擴充 TileService 類別。

class MyQSTileService : TileService() {

    // Called when the user adds your tile.
    override fun onTileAdded() {
        super.onTileAdded()
    }
    // Called when your app can update your tile.
    override fun onStartListening() {
        super.onStartListening()
    }

    // Called when your app can no longer update your tile.
    override fun onStopListening() {
        super.onStopListening()
    }

    // Called when the user taps on your tile in an active or inactive state.
    override fun onClick() {
        super.onClick()
    }
    // Called when the user removes your tile.
    override fun onTileRemoved() {
        super.onTileRemoved()
    }
}

在應用程式的資訊清單檔案中宣告 TileService。新增 TileService 的名稱和標籤 (android:label 最多 18 個字元)、您在上一節建立的自訂圖示,以及適當的權限。

 <!-- 18-character limit for android:label. -->
 <service
     android:name=".MyQSTileService"
     android:exported="true"
     android:label="@string/my_default_tile_label"
     android:icon="@drawable/my_default_icon_label"
     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
     <intent-filter>
         <action android:name="android.service.quicksettings.action.QS_TILE" />
     </intent-filter>
 </service>

管理 TileService

在應用程式資訊清單中建立並宣告 TileService 後,您必須管理其狀態。

TileService 是繫結服務。當應用程式要求或系統需要與 通訊時,系統會繫結 TileService。典型的繫結服務生命週期包含下列四種回呼方法:onCreate()、onBind()、onUnbind() 和 onDestroy()。每當服務進入新的生命週期階段,系統就會叫用這些方法。

TileService 生命週期總覽

除了控制繫結服務生命週期的回呼外,您還必須實作 TileService 生命週期的其他方法。這些方法可能會在 onCreate() 和 onDestroy() 之外呼叫,因為 Service 生命週期方法和 TileService 生命週期方法是在兩個不同的非同步執行緒中呼叫。

TileService 生命週期包含下列方法,每當 TileService 進入新的生命週期階段時,系統就會叫用這些方法:

  • onTileAdded():只有在使用者首次新增資訊方塊,以及使用者移除並再次新增資訊方塊時,系統才會呼叫這個方法。此時最適合執行任何一次性初始化作業。不過,這可能無法滿足所有必要的初始化作業。

  • onStartListening() 和 onStopListening():每當應用程式更新資訊方塊時,系統就會呼叫這些方法,而且呼叫頻率很高。TileService 會在 onStartListening() 和 onStopListening() 之間保持繫結,讓應用程式可以修改資訊方塊並推送更新。

  • onTileRemoved():只有在使用者移除資訊方塊時,系統才會呼叫這個方法。

選取聆聽模式

TileService 會在「主動」模式或「非主動」模式下聆聽。建議您使用主動模式,但必須在應用程式資訊清單中宣告。否則,TileService 為標準模式,不需要宣告。

請勿假設 TileService 會在 onStartListening() 和 onStopListening() 方法組合之外運作。

如果 TileService 會在自己的程序中監聽及監控狀態,請使用主動模式。處於啟用模式的 TileService 會繫結至 onTileAdded()、onTileRemoved()、輕觸事件,以及應用程式程序要求時。

如果 TileService 會在資訊方塊狀態應由其程序更新時收到通知,建議使用主動模式。使用中的資訊方塊可限制系統負擔,因為每次使用者看到「快速設定」面板時,不一定要繫結資訊方塊。

您可以呼叫靜態 TileService.requestListeningState() 方法,要求啟動聆聽狀態,並接收 onStartListening() 的回呼。

如要宣告主動模式,請在應用程式的資訊清單檔案中加入 META_DATA_ACTIVE_TILE。

<service ...>
    <meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
       android:value="true" />
    ...
</service>

未啟用模式

非主動模式為標準模式。如果 TileService 在使用者看到資訊方塊時繫結,則處於非啟用模式。這表示 TileService 可能會在無法控制的時間建立及重新繫結。當使用者未查看資訊方塊時,系統也可能會解除繫結並刪除。

使用者開啟「快速設定」面板後,應用程式會收到 onStartListening() 的回呼。在 onStartListening() 和 onStopListening() 之間,您可以視需要多次更新 Tile 物件。

您不需要宣告非主動模式:請勿在應用程式的資訊清單檔案中加入 META_DATA_ACTIVE_TILE。

資訊方塊狀態總覽

使用者新增資訊方塊後,資訊方塊一律會處於下列其中一種狀態。

  • STATE_ACTIVE:表示開啟或啟用狀態。使用者可以在這個狀態下與資訊方塊互動。

    舉例來說,如果運動應用程式資訊方塊可讓使用者啟動計時健身課程,STATE_ACTIVE 就表示使用者已啟動健身課程,且計時器正在執行。

  • STATE_INACTIVE:表示關閉或暫停狀態。使用者可以在這個狀態下與資訊方塊互動。

    再次以運動應用程式資訊方塊為例,STATE_INACTIVE 中的資訊方塊表示使用者尚未啟動健身課程,但可以視需要啟動。

  • STATE_UNAVAILABLE:表示暫時無法使用的狀態。使用者無法在這種狀態下與資訊方塊互動。

    舉例來說,STATE_UNAVAILABLE 中的圖塊表示使用者目前因某些原因無法使用該圖塊。

系統只會設定 Tile 物件的初始狀態。您可以在 Tile 物件生命週期的其餘部分設定其狀態。

系統可能會為圖塊圖示和背景著色,反映 Tile 物件的狀態。設為 STATE_ACTIVE 的 Tile 物件最暗,STATE_INACTIVE 和 STATE_UNAVAILABLE 則越來越亮。確切色調取決於製造商和版本。

VPN 圖塊會根據物件狀態改變色調
圖 4. 資訊方塊著色範例,反映資訊方塊狀態 (分別為啟用、停用和無法使用狀態)。

更新資訊方塊

收到 onStartListening() 的回呼後,即可更新資訊方塊。視資訊方塊的模式而定,資訊方塊至少可以更新一次,直到收到 onStopListening() 的回呼為止。

在主動模式下,您可以在收到 onStopListening() 回呼前更新一次圖塊。在非啟用模式下,您可以在 onStartListening() 到 onStopListening() 之間不限次數更新資訊方塊。

您可以呼叫 getQsTile() 來擷取 Tile 物件。如要更新 Tile 物件的特定欄位,請呼叫下列方法:

將 Tile 物件的欄位設為正確的值後,您必須呼叫 updateTile() 來更新資訊方塊。系統會剖析更新後的資訊方塊資料,並更新使用者介面。

data class StateModel(val enabled: Boolean, val label: String, val icon: Icon)

override fun onStartListening() {
    super.onStartListening()
    val state = getStateFromService()
    qsTile.label = state.label
    qsTile.contentDescription = state.label
    qsTile.state = if (state.enabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
    qsTile.icon = state.icon
    qsTile.updateTile()
}

處理輕觸動作

如果資訊方塊位於 STATE_ACTIVE 或 STATE_INACTIVE,使用者可以輕觸資訊方塊來觸發動作。系統接著會叫用應用程式的 onClick() 回呼。

應用程式收到 onClick() 的回呼後,即可啟動對話方塊或活動、觸發背景工作,或變更資訊方塊的狀態。

var counter = 0
override fun onClick() {
    super.onClick()
    counter++
    qsTile.state = if (counter % 2 == 0) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
    qsTile.label = "Clicked $counter times"
    qsTile.contentDescription = qsTile.label
    qsTile.updateTile()
}

啟動對話方塊

showDialog() 會收合「快速設定」面板,並顯示對話方塊。如果動作需要額外輸入內容或使用者同意,請使用對話方塊為動作新增背景資訊。

啟動活動

startActivityAndCollapse() 會在收合面板時啟動活動。如果需要顯示比對話方塊更詳細的資訊,或動作具有高度互動性,則適合使用活動。

如果應用程式需要大量使用者互動,則應將啟動活動視為最後手段。建議改用對話方塊或切換鈕。

長按圖塊會向使用者顯示「App Info」畫面。如要覆寫這項行為,改為啟動設定偏好設定的活動,請使用 ACTION_QS_TILE_PREFERENCES 將 <intent-filter> 新增至其中一個活動。

從 Android API 28 開始,PendingIntent 必須具備 Intent.FLAG_ACTIVITY_NEW_TASK:

if (Build.VERSION.SDK_INT >= 28) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

您也可以在特定 Activity 區段的 AndroidManifest.xml 中新增標記。

將圖塊標示為可切換

如果資訊方塊主要做為雙狀態切換鈕 (這是資訊方塊最常見的行為),建議您將資訊方塊標示為可切換。這有助於向作業系統提供資訊,說明資訊方塊的行為,並改善整體無障礙功能。

將 TOGGLEABLE_TILE 中繼資料設為 true,將資訊方塊標示為可切換。

<service ...>
  <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
    android:value="true" />
</service>

在安全鎖定的裝置上,只執行安全的操作

在鎖定的裝置上,資訊方塊可能會顯示在螢幕鎖定畫面頂端。如果圖塊包含私密資訊,請檢查 isSecure() 的值,判斷裝置是否處於安全狀態,並據此變更 TileService 的行為。

如果可在鎖定狀態下安全執行資訊方塊動作,請使用 startActivity() 在螢幕鎖定畫面上啟動活動。

如果資訊方塊動作不安全,請使用 unlockAndRun() 提示使用者解鎖裝置。如果成功,系統會執行您傳入這個方法的 Runnable 物件。

為動態磚分類

如要提升「快速設定」的使用者體驗,可以將圖塊分類。系統會將資訊方塊分類,例如「連線」、「螢幕」和「隱私權」。系統會使用這些類別,在「快速設定」編輯模式中排序及分組設定方塊,方便使用者尋找及管理。

導入作業

如要為 TileService 指定類別,請在 AndroidManifest.xml 檔案的服務宣告中新增中繼資料欄位:

  • 在 AndroidManifest.xml 中,在 TileService 的 <service> 元素中,新增 <meta-data> 元素。
  • android:name:將這個值設為 android.service.quicksettings.TILE_CATEGORY。
  • android:value:指派其中一個預先定義的類別常數,例如 android.service.quicksettings.CATEGORY_CONNECTIVITY 或 android.service.quicksettings.CATEGORY_DISPLAY。

如以下範例所示:

<service
    android:name=".MyConnectivityTileService"
    [...]
    >
    <meta-data android:name="android.service.quicksettings.TILE_CATEGORY"
        android:value="android.service.quicksettings.CATEGORY_CONNECTIVITY" />
</service>

API 提供一組預先定義的類別供您選擇。這些類別在 TileService 類別中定義為字串常數。

如未指定類別,系統會自動指派預設類別:

  • 來自系統應用程式:屬於系統應用程式的資訊方塊。
  • 來自安裝的應用程式:使用者安裝的應用程式提供的資訊方塊。

雖然 Google Pixel 裝置會在快速設定中使用類別,但原始設備製造商 (OEM) 可以在各自的系統 UI 中使用或忽略這項類別資訊。

提示使用者新增資訊方塊

如要手動新增資訊方塊,使用者必須按照下列步驟操作:

  1. 向下滑動,開啟「快速設定」面板。
  2. 輕觸「編輯」按鈕。
  3. 捲動裝置上的所有圖塊,直到找到你的圖塊。
  4. 按住圖塊,然後拖曳到使用中圖塊清單。

使用者也可以隨時移動或移除資訊方塊。

自 Android 13 起,您可以使用 requestAddTileService() 方法,讓使用者更輕鬆地將圖塊新增至裝置。這個方法會提示使用者,要求他們直接將設定方塊快速新增至「快速設定」面板。提示包含應用程式名稱、提供的標籤和圖示。

快速設定放置 API 提示
圖 5. 快速設定放置 Places API 提示。
public void requestAddTileService (
  ComponentName tileServiceComponentName,
  CharSequence tileLabel,
  Icon icon,
  Executor resultExecutor,
  Consumer<Integer> resultCallback
)

回呼包含的資訊包括:圖塊是否已新增、未新增、是否已存在,或是否發生任何錯誤。

請自行斟酌提示使用者的時機和頻率。建議您只在特定情境下呼叫 requestAddTileService(),例如使用者首次與資訊方塊提供的功能互動時。

如果使用者先前已多次拒絕特定 ComponentName,系統可以選擇停止處理該權限的要求。系統會根據用於擷取這項服務的 Context 判斷使用者,且必須與目前的使用者相符。