支援不同的螢幕大小

如果應用程式支援各種螢幕大小,就能以多種裝置和最多使用者存取您的應用程式。

如果想盡可能支援各種螢幕大小,就需要讓應用程式版面配置採用回應式及調適性設計。無論螢幕大小為何,回應式/調適性版面配置都可以提供最佳的使用者體驗,讓應用程式能配合手機、平板電腦、折疊式裝置、ChromeOS 裝置、直向和橫向螢幕方向,以及可調整大小的設定 (例如多視窗模式)。

回應式/自動調整版面配置會配合可用的顯示空間而變化。變更範圍包括填滿空間的小幅版面配置 (回應式設計),改為完全取代某個版面配置,以便您的應用程式能最適合各種螢幕大小 (自動調整設計)。

Jetpack Compose 是宣告式 UI 工具包,非常適合用於設計及實作版面配置,以動態方式變更,以便在各種螢幕大小以不同方式轉譯內容。

明確配合螢幕層級可組合項大幅變更版面配置

使用 Compose 安排整個應用程式的版面時,應用程式層級和畫面層級可組合項佔用應用程式轉譯的所有空間。在這個設計層級中,變更螢幕的整體版面配置,以便利用較大的螢幕,是合理的做法。

避免使用實體、硬體數值來決定版面配置。您可能會想根據固定的實際值做出決策 (裝置是平板電腦嗎?實體螢幕是否有特定顯示比例?),但這些問題的答案對於判斷 UI 可以使用的空間可能沒有幫助。

這張圖表顯示多種不同裝置板型規格,包括手機、折疊式裝置、平板電腦和筆記型電腦。
圖 1. 手機、折疊式裝置、平板電腦和筆記型電腦板型規格

在平板電腦中,應用程式可能會以多視窗模式執行,這時應用程式可能會在畫面上與其他應用程式並排顯示;在 ChromeOS 中,應用程式可能會在可調整大小的視窗中執行。裝置上甚至可能有多個實體螢幕,例如折疊式裝置。在這些情況下,實體螢幕大小與決定內容的顯示方式無關。

您必須改為根據實際分配給應用程式的畫面部分 (例如 Jetpack WindowManager 程式庫所提供的目前視窗指標) 做決定。如要瞭解如何在 Compose 應用程式中使用 WindowManager,請參考 JetNews 範例。

遵循這個方法,可以讓您的應用程式更加具有彈性,在上述所有情境中都能順利運作。此外,如果版面配置可因應螢幕空間調整,系統就能降低特殊處理工作量,輕鬆支援 ChromeOS 等平台和平板電腦與折疊式裝置等板型規格。

觀察應用程式可用的相關空間後,建議您將原始大小轉換為有意義的大小類別,如視窗大小類別中所述。這可以將尺寸分成不同的標準尺寸值區。這些值區是一種中斷點,設計目的是要讓您在最佳化應用程式時兼顧簡單與靈活,以滿足最獨特的情境需求。這些大小類別是指應用程式的整體視窗,因此請使用這些類別來決定會影響整體螢幕版面配置的版面配置決策。您可以將這些大小類別當成狀態向下傳遞,或者,也可以執行額外邏輯以建立衍生狀態,再向下傳遞到巢狀可組合項中。

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

這種分層方法會將螢幕大小邏輯限制為單一位置,而不會將其分散在應用程式需要保持同步的許多位置。這個單一位置會產生狀態,您可以明確向下傳遞至其他可組合項,就像處理其他應用程式狀態一樣。明確傳遞狀態可簡化個別可組合項,因為這些可組合項會是一般可組合函式,伴隨其他資料攜帶著大小類別或指定的設定。

彈性巢狀可組合項可重複使用

要是可組合項可以放置在許多不同位置,就能提升重複使用的可能性。如果可組合項預設為一律以特定大小放在特定位置,就很難重複用於任何其他位置,也不太容易搭配其他可用空間使用。這也表示可重複使用的個別可組合項應避免仰賴「全域」大小資訊決定運作方式

請參考以下範例:想像一個巢狀可組合項導入了「清單 - 詳細資料」版面配置,畫面可能會顯示一個窗格或兩個並排的窗格。

應用程式並排顯示兩個窗格的螢幕截圖。
圖 2. 顯示一般清單/詳細資料版面配置的應用程式螢幕截圖:1 是清單區域;2 為詳細資料區域。

我們希望這項決策屬於應用程式整體版面配置的一部分,因此如前所述,我們會將決策從螢幕層級可組合項向下傳遞:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

如果我們想改為讓可組合項根據可用空間獨立變更版面配置,該怎麼做?例如,資訊卡,我們希望在空間允許的情況下,顯示其他詳細資料。我們想根據某些可用大小執行某些邏輯,但具體來說是哪個大小?

兩張不同的資訊卡範例。
圖 3. 狹窄的資訊卡只顯示圖示和標題,較寬的資訊卡則顯示圖示、標題和說明。

如上方所示,請避免將尺寸設為裝置的實際螢幕大小,因為這麼做並不適用於多螢幕裝置,也不適用於非全螢幕的應用程式。

由於可組合項並非螢幕層級可組合項,為了盡量提升重複使用的可能性,我們也不應直接使用目前的視窗指標。如果元件用於邊框間距 (例如插邊),或應用程式有導覽邊欄或應用程式列等元件,可組合項的可用空間量與應用程式的整體可用空間可能會有極大差異。

因此,我們應使用實際為可組合項指定的寬度來算繪可組合項本身。有兩種方法可以取得這項寬度資訊:

如要變更內容的顯示位置顯示方式,您可以使用一系列的修飾符,也可以使用自訂版面配置建構回應式版面配置。這可以簡單得像拿一些子項填滿所有可用空間,或是在有足夠空間的情況下,以多個資料欄擺放子項。

如要變更顯示的內容,可以使用 BoxWithConstraints,而且這個替代方法更加有效。這個可組合項提供成效評估限制,可讓您用來根據可用空間呼叫不同的可組合項。不過,這樣做可能會產生一些費用,因為 BoxWithConstraints 會將組合作業延後到版面配置階段,也就是當已知這些限制條件時,進而導致版面配置期間執行更多工作。

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

確保所有資料皆可用於各種尺寸

在運用額外螢幕空間時,比起小型螢幕,大型螢幕可能會有空間向使用者顯示更多內容。導入這種行為的可組合項時,您可能會想提高效率,並載入資料做為目前大小的副作用。

不過,這樣做與單向資料流原則牴觸,在單向資料流中,資料可以提升並提供給可組合項,以便適當進行算繪。必須為可組合項提供足夠資料,讓可組合項隨時都具備在任何大小螢幕上顯示內容所需的材料,即使其中部分資料可能不會每次都用到。

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Card 範例為基礎,請注意,我們一律會將 description 傳遞至 Card。雖然 description 只有在寬度允許顯示時才會使用,但無論可用寬度為何,Card 一律會要求這個項目。

一律傳遞資料會降低自動調整式版面配置的有狀態程度,使其變得較簡單,避免在切換不同大小時觸發副作用 (這可能會發生在調整視窗大小、螢幕方向改變,或折疊及展開裝置時)。

這個原則也讓您可以在版面配置發生變化時保留狀態。由於資訊可能不適用於所有螢幕尺寸,因此「提升」(hoisting) 這類資訊後,我們就能在版面配置大小變更時保留使用者的狀態。舉例來說,我們可以提升 showMore 布林值標記,以便在調整大小導致版面配置在隱藏和顯示說明之間切換時保留使用者的狀態:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

瞭解詳情

如要進一步瞭解 Compose 中的自訂版面配置,請參閱以下其他資源。

範例應用程式

  • 大螢幕標準版面配置是經實證的設計模式存放區,可在大螢幕裝置上提供最佳使用者體驗
  • JetNews 展示如何設計可調整 UI 的應用程式,以便運用可用空間。
  • Reply (回覆) 是一種自動調整範例,支援行動裝置、平板電腦和摺疊式裝置
  • Now in Android:此應用程式採用自動調整式版面配置,支援不同螢幕大小

影片