使用 Glance 建構 UI

本頁說明如何處理尺寸,並提供彈性靈活的回應式功能 透過現有的 Glance 元件使用 Glance 版面配置

使用BoxColumnRow

Glance 有三個主要的可組合函式版面配置:

  • Box:將元素放在另一個元素上。這會轉譯為 RelativeLayout

  • Column:將元素沿著垂直軸相鄰。可翻譯 設為直向的 LinearLayout

  • Row:將元素沿著水平軸相鄰。可翻譯 轉換為水平方向的 LinearLayout

Glance 支援 Scaffold 物件。放置您的ColumnRow和 指定 Scaffold 物件中的 Box 可組合函式。

欄、列和方塊版面配置的圖片。
圖 1. 含 Column、Row 和 Box 的版面配置範例。

這些可組合函式分別可讓您定義垂直和水平對齊方式 內容以及寬度、高度、粗細或邊框間距限制 修飾符。此外,每個子項都可以定義修飾符來變更空格 以及上層刊登位置

以下範例說明如何建立 Row,以便平均分配 其子項的水平,如圖 1 所示:

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

Row 會填滿可用寬度上限,因為每個子項都相同 就會平均共用可用空間您可以設定不同的權重 大小、邊框間距或對齊方式,以配合需求調整版面配置。

使用可捲動版面配置

提供回應式內容的另一種方式是將其設為可捲動。這是 透過 LazyColumn 可組合函式執行這些動作這個可組合函式可讓您定義 。

以下程式碼片段說明在 LazyColumn

您可以提供項目數量:

// Remember to import Glance Composables
// import androidx.glance.appwidget.layout.LazyColumn

LazyColumn {
    items(10) { index: Int ->
        Text(
            text = "Item $index",
            modifier = GlanceModifier.fillMaxWidth()
        )
    }
}

提供個別項目:

LazyColumn {
    item {
        Text("First Item")
    }
    item {
        Text("Second Item")
    }
}

提供項目的清單或陣列:

LazyColumn {
    items(peopleNameList) { name ->
        Text(name)
    }
}

您也可以合併使用上述範例:

LazyColumn {
    item {
        Text("Names:")
    }
    items(peopleNameList) { name ->
        Text(name)
    }

    // or in case you need the index:
    itemsIndexed(peopleNameList) { index, person ->
        Text("$person at index $index")
    }
}

請注意,先前的程式碼片段並未指定 itemId。指定 itemId 有助提升效能並維持捲動頁面 透過清單位置,以及從 Android 12 以上版本更新 appWidget (適用於 例如新增或移除清單中的項目時)。以下範例 說明如何指定 itemId

items(items = peopleList, key = { person -> person.id }) { person ->
    Text(person.name)
}

定義 SizeMode

AppWidget 尺寸可能會因裝置、使用者選擇或啟動器而異。 因此,請務必按照提供 彈性的小工具版面配置頁面。Glance 利用 SizeMode 簡化這件事。 定義和 LocalSize 值以下各節將說明 。

SizeMode.Single

預設模式為 SizeMode.Single。表示只有一種 內容;也就是即使 AppWidget 可用的大小有所變更 內容大小維持不變

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Single

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the minimum size or resizable
        // size defined in the App Widget metadata
        val size = LocalSize.current
        // ...
    }
}

使用這種模式時,請確認以下事項:

  • 中繼資料值的最小和最大尺寸必須正確定義 配合內容大小
  • 在預期大小範圍內,內容的靈活度夠高。

一般來說,只要符合下列任一條件,建議您使用這個模式:

a) AppWidget 有固定大小,或 b) 調整大小時,不會變更其內容。

SizeMode.Responsive

這個模式相當於提供回應式版面配置,以允許 GlanceAppWidget 可定義一組符合特定條件的回應式版面配置 大小。針對每個已定義的大小,系統會建立內容並對應至特定的 AppWidget 建立或更新時的大小。接著,系統會選取 選出最適當的尺寸。

舉例來說,在目的地 AppWidget 中,您可以定義三種大小 內容:

class MyAppWidget : GlanceAppWidget() {

    companion object {
        private val SMALL_SQUARE = DpSize(100.dp, 100.dp)
        private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
        private val BIG_SQUARE = DpSize(250.dp, 250.dp)
    }

    override val sizeMode = SizeMode.Responsive(
        setOf(
            SMALL_SQUARE,
            HORIZONTAL_RECTANGLE,
            BIG_SQUARE
        )
    )

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be one of the sizes defined above.
        val size = LocalSize.current
        Column {
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            }
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width >= HORIZONTAL_RECTANGLE.width) {
                    Button("School")
                }
            }
            if (size.height >= BIG_SQUARE.height) {
                Text(text = "provided by X")
            }
        }
    }
}

在上一個範例中,系統會呼叫 provideContent 方法三次,並 就會對應至已定義的大小

  • 在第一次呼叫中,大小評估為 100x100。內容沒有 包括額外按鈕,以及頂端和底部文字。
  • 在第二次呼叫中,大小評估為 250x100。內容包括 額外按鈕,但不包括頂端和底部文字。
  • 在第三次呼叫中,大小評估為 250x250。內容包括 多餘按鈕,以及文字。

SizeMode.Responsive 是其他兩種模式的組合,可讓您 在預先定義範圍內定義回應式內容。一般而言,這個模式 執行效果更好,且可在 AppWidget 調整大小時更順暢。

下表顯示尺寸值,具體取決於 SizeModeAppWidget 的可用大小:

可用大小 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Single 110 x 110 110 x 110 110 x 110 110 x 110
SizeMode.Exact 105 x 110 203 x 112 72 x 72 203 x 150
SizeMode.Responsive 80 x 100 80 x 100 80 x 100 150 x 120
* 確切的值僅供示範之用。

SizeMode.Exact

SizeMode.Exact 等同於提供確切的版面配置, 每次可用的 AppWidget 大小時,要求 GlanceAppWidget 內容 變更 (例如使用者調整主畫面上的 AppWidget 大小時)。

舉例來說,在目標小工具中,您可以在 可用寬度大於某個值。

class MyAppWidget : GlanceAppWidget() {

    override val sizeMode = SizeMode.Exact

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // ...

        provideContent {
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        // Size will be the size of the AppWidget
        val size = LocalSize.current
        Column {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button()
                Button()
                if (size.width > 250.dp) {
                    Button("School")
                }
            }
        }
    }
}

這個模式提供更多彈性 注意事項:

  • 每次大小變更時,都必須完整重新建立 AppWidget。這個 如果內容複雜,可能會導致效能問題和使用者介面跳動。
  • 可用大小可能會因啟動器的實作方式而異。 舉例來說,如果啟動器未提供大小清單,則 可能的大小
  • 在 Android 12 以下版本的裝置上,大小計算邏輯可能並不適用 情境。

一般而言,如果 SizeMode.Responsive 無法使用,應使用這個模式 (也就是說,少數的回應式版面配置不適合)。

取得實用資源

使用 LocalContext.current 存取任何 Android 資源,如 範例:

LocalContext.current.getString(R.string.glance_title)

建議您直接提供資源 ID,以減少最終圖像的大小 RemoteViews 物件,並啟用動態資源,例如動態資源 顏色

可組合元件和方法接受使用「提供者」的資源,例如 ImageProvider,或是使用超載方法 GlanceModifier.background(R.color.blue)。例如:

Column(
    modifier = GlanceModifier.background(R.color.default_widget_background)
) { /**...*/ }

Image(
    provider = ImageProvider(R.drawable.ic_logo),
    contentDescription = "My image",
)

處理文字

Glance 1.1.0 包含用來設定文字樣式的 API。使用以下字詞設定文字樣式: TextStyle 類別的 fontSizefontWeightfontFamily 屬性。

fontFamily 支援所有系統字型,如以下範例所示,但 系統不支援在應用程式中使用自訂字型:

Text(
    style = TextStyle(
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
        fontFamily = FontFamily.Monospace
    ),
    text = "Example Text"
)

新增複合按鈕

Android 12 導入複合按鈕。資訊一覽支援回溯功能 與以下類型的複合按鈕相容:

這些複合按鈕會顯示可點擊的檢視區塊,代表 「已勾選」state.

var isApplesChecked by remember { mutableStateOf(false) }
var isEnabledSwitched by remember { mutableStateOf(false) }
var isRadioChecked by remember { mutableStateOf(0) }

CheckBox(
    checked = isApplesChecked,
    onCheckedChange = { isApplesChecked = !isApplesChecked },
    text = "Apples"
)

Switch(
    checked = isEnabledSwitched,
    onCheckedChange = { isEnabledSwitched = !isEnabledSwitched },
    text = "Enabled"
)

RadioButton(
    checked = isRadioChecked == 1,
    onClick = { isRadioChecked = 1 },
    text = "Checked"
)

當狀態變更時,就會觸發提供的 lambda。您可以將 檢查狀態,如以下範例所示:

class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val myRepository = MyRepository.getInstance()

        provideContent {
            val scope = rememberCoroutineScope()

            val saveApple: (Boolean) -> Unit =
                { scope.launch { myRepository.saveApple(it) } }
            MyContent(saveApple)
        }
    }

    @Composable
    private fun MyContent(saveApple: (Boolean) -> Unit) {

        var isAppleChecked by remember { mutableStateOf(false) }

        Button(
            text = "Save",
            onClick = { saveApple(isAppleChecked) }
        )
    }
}

您也可以將 colors 屬性提供給 CheckBoxSwitchRadioButton 自訂顏色:

CheckBox(
    // ...
    colors = CheckboxDefaults.colors(
        checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight),
        uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked }
)

Switch(
    // ...
    colors = SwitchDefaults.colors(
        checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan),
        uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta),
        checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow),
        uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green)
    ),
    checked = isChecked,
    onCheckedChange = { isChecked = !isChecked },
    text = "Enabled"
)

RadioButton(
    // ...
    colors = RadioButtonDefaults.colors(
        checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow),
        uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue)
    ),

)

其他元件

Glance 1.1.0 包含其他元件的發布內容,如 資料表:

名稱 圖片 參考資料連結 其他注意事項
實心按鈕 alt_text 元件
外框按鈕 alt_text 元件
圖示按鈕 alt_text 元件 主要 / 次要 / 純圖示
標題列 alt_text 元件
Scaffold Scaffold 和標題列是同一個示範。