1. 簡介
在 Android 平台中開發應用程式的主要優點之一,就是能廣泛觸及各種板型規格的使用者,例如穿戴式裝置、折疊式裝置、平板電腦、桌上型電腦,甚至電視等。使用應用程式時,使用者可能會希望在大螢幕裝置中也可以使用相同的應用程式,以便充分運用可用空間。有越來越多的 Android 使用者會在不同螢幕尺寸的裝置中使用應用程式,並希望在所有裝置中都能享有高品質的使用者體驗。
到目前為止,您已經瞭解如何打造適合行動裝置使用的應用程式。在本程式碼研究室中,您將瞭解如何轉換應用程式,讓應用程式可以依照不同螢幕大小調整使用。您將使用可自動調整的導覽版面配置模式,這種模式不但美觀,而且在行動裝置和大螢幕裝置 (例如:折疊式裝置、平板電腦和桌上型電腦) 中都可使用。
必要條件
- 熟悉 Kotlin 程式設計,包含類別、函式和條件式
- 熟悉如何使用
ViewModel
類別 - 熟悉如何建立
Composables
函式 - 瞭解如何使用 Jetpack Compose 建構版面配置
- 瞭解如何使用裝置或模擬器執行應用程式
課程內容
- 如何在沒有導覽圖的情況下為簡易應用程式建立畫面之間的導覽
- 如何使用 Jetpack Compose 建立自動調整導覽版面配置
- 如何建立自訂返回處理常式
建構項目
- 您將在現有的 Reply 應用程式中實作動態導覽,將其版面配置調整為適合所有螢幕大小
完成的作品應如下圖所示:
軟硬體需求
- 可連接網際網路且有網頁瀏覽器的電腦、Android Studio
- GitHub 存取權
2. 應用程式總覽
Reply 應用程式簡介
Reply 應用程式是功能類似電子郵件用戶端的多螢幕應用程式。
其中包含 4 種不同的類別,而且會以不同的分頁應用程式中包含 4 種不同的類別,而且會以不同的分頁標籤顯示,分別是:收件匣、寄件備份、草稿和垃圾郵件。標籤顯示,分別是:收件匣、寄件備份、草稿和垃圾郵件。
下載範例程式碼
在 Android Studio 中開啟 basic-android-kotlin-compose-training-reply-app
資料夾。
- 前往為專案提供的 GitHub 存放區頁面。
- 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下方螢幕截圖中,分支版本名稱為「main」。
- 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面就會顯示彈出式視窗。
- 在彈出式視窗中,按一下「Download ZIP」(下載 ZIP) 按鈕,將專案儲存至電腦。等待下載作業完成。
- 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
- 按兩下 ZIP 檔案,將檔案解壓縮。這項操作會建立含有專案檔案的新資料夾。
在 Android Studio 中開啟專案
- 啟動 Android Studio。
- 在「Welcome to Android Studio」(歡迎使用 Android Studio) 視窗中,按一下「Open」(開啟)。
注意:如果已開啟 Android Studio,請依序選取「File」(檔案) >「Open」(開啟) 選單選項。
- 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「下載」資料夾中)。
- 按兩下該專案資料夾。
- 等待 Android Studio 開啟專案。
- 按一下「Run」按鈕 即可建構並執行應用程式。請確認應用程式的建構符合預期。
3. 範例程式碼逐步操作說明
Reply 應用程式中的重要目錄
Reply 應用程式專案的資料和使用者介面層會分為不同的目錄。ReplyViewModel
、ReplyUiState
和其他可組合項位於 ui
目錄中。定義資料層和資料供應商類別的 data
和 enum
類別位於 data
目錄中。
Reply 應用程式中的資料初始化
系統會透過 ReplyViewModel
中的 initilizeUIState()
方法 (在 init
函式中執行),使用相關資料來初始化 Reply 應用程式。
ReplyViewModel.kt
...
init {
initializeUIState()
}
private fun initializeUIState() {
var mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
...
畫面層級可組合項
與其他應用程式一樣,Reply 應用程式會使用 ReplyApp
可組合項做為主要的可組合項,並於其中宣告 viewModel
和 uiState
。不同的 viewModel
函式也會做為 ReplyHomeScreen
可組合項的 lambda 引數傳遞。
ReplyApp.kt
...
@Composable
fun ReplyApp(modifier: Modifier = Modifier) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
ReplyHomeScreen(
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
}
其他可組合項
ReplyHomeScreen.kt
包含主畫面的畫面可組合項,包括導覽元素在內。ReplyHomeContent.kt
包含可組合項,這些可定義更詳細的主畫面可組合項。ReplyDetailsScreen.kt
包含畫面可組合項及較小的細節畫面可組合項。
您可以細讀每個檔案以進一步瞭解可組合項,然後再繼續程式碼研究室接下來的章節。
4. 變更沒有導覽圖的畫面
在先前的課程中,您已瞭解如何使用 NavHostController
類別在不同畫面之間導覽。透過 Compose,您也可以利用執行階段可變動狀態,使用簡易條件陳述式變更畫面。這對於小型應用程式 (例如只要在兩個畫面之間切換的 Reply 應用程式) 而言特別實用。
變更有狀態異動的畫面
在 Compose 中,當狀態有所異動時,系統就會重組畫面。您可以使用簡單的條件式來變更畫面,藉此回應狀態異動。
當使用者位於主畫面時,您將使用條件式顯示主畫面的內容,然後在使用者離開主畫面時顯示詳細資料畫面。
完成下列步驟,即可修改 Reply 應用程式以允許狀態異動時的畫面變更:
- 在 Android Studio 中開啟範例程式碼。
- 在
ReplyHomeScreen.kt
的ReplyHomeScreen
可組合項中,當replyUiState
物件的isShowingHomepage
屬性為true
時,使用if
陳述式納入ReplyAppContent
可組合項。
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
您現在必須以顯示詳細資料畫面的方式,將使用者離開主畫面時的情況納入考量。
- 新增在主體中已經含有
ReplyDetailsScreen
可組合項的else
分支版本。新增replyUIState
、onDetailScreenBackPressed
和modifier
做為ReplyDetailsScreen
可組合項的引數。
ReplyHomeScreen.kt
@Composable
fun ReplyHomeScreen(
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Int) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
replyUiState
物件是狀態物件。因此,如果 replyUiState
物件的 isShowingHomepage
屬性有變更,在執行階段時,ReplyHomeScreen
可組合項就會重組,且 if/else
陳述式也會重新評估。此方法不需要使用 NavHostController
類別,即可支援不同畫面之間的導覽。
建立自訂返回處理常式
使用 NavHost
可組合項在畫面之間切換的其中一個好處,就是可以將上一個畫面的方向儲存在返回堆疊中。這些已儲存的畫面可讓系統返回按鈕在叫用時輕鬆導覽至上一個畫面。由於 Reply 應用程式沒有使用 NavHost
,因此您必須新增程式碼才能手動處理返回按鈕。這是您接下來要完成的工作。
如要在 Reply 應用程式中建立自訂返回處理常式,請完成下列步驟:
- 在
ReplyDetailsScreen
可組合項的第一行中新增BackHandler
可組合項。 - 在
BackHandler
可組合項主體中呼叫onBackPressed()
函式。
ReplyDetailsScreen.kt
...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
) {
BackHandler {
onBackPressed()
}
...
5. 在大螢幕裝置中執行應用程式
使用可調整大小的模擬器檢查應用程式
為了製作可用應用程式,開發人員必須瞭解各種板型規格的使用者體驗。因此,您必須在開發程序一開始就針對各種板型規格測試應用程式。
您可以使用不同螢幕尺寸的模擬器來達成這個目標。但是這樣做可能會相當麻煩,尤其如果要同時針對多種螢幕大小建構時更是如此。您可能也需要測試執行中的應用程式如何依照螢幕大小變更,例如螢幕方向變更、桌上型電腦中的視窗大小變更,以及折疊式裝置的折疊狀態變更。
Android Studio 推出了可調整大小的模擬器,可協助您針對這些不同的情況進行測試。
如要設定可調整大小的模擬器,請完成下列步驟:
- 確認您使用的是 Android Studio Chipmunk | 2021.2.1 以上版本。
- 在 Android Studio 中,依序選取「Tools」>「Device Manager」。
- 在「Device Manager」中,按一下「Create device」。
- 依序選取「Phone」類別和「Resizable (Experimental)」裝置。
- 按一下「Next」。
- 選取「API Level 33」。
- 按一下「Next」。
- 為新的 Android 虛擬裝置命名。
- 按一下「Finish」。
在大螢幕模擬器中執行應用程式
現在您已設定可調整大小的模擬器,接下來我們要來看看應用程式在大螢幕中的呈現效果。
- 在可調整大小的模擬器中執行應用程式。
- 選取「Tablet」做為顯示模式。
- 在平板電腦模式中,以橫向模式檢查應用程式。
您會發現平板電腦螢幕顯示的畫面朝水平方向拉長。雖然此螢幕方向的功能運作正常,但可能不適合大螢幕裝置使用。所以接下來我們要解決這個問題。
針對大螢幕設計
在平板電腦中查看此應用程式時,您的第一個想法會是這個應用程式不但設計不良,而且缺乏吸引力。沒錯,此版面配置「並非」為大螢幕使用而設計。
為大螢幕 (例如平板電腦和折疊式裝置) 使用體驗設計時,您必須考量使用者人體工學,以及使用者手指與螢幕的距離。使用行動裝置時,使用者可以使用手指輕鬆存取畫面中的多數內容,而且互動元素 (例如按鈕和導覽元素) 的位置也不會帶來太大影響。但使用大型螢幕時,畫面中央如果有重要的互動元素,使用時可能會較為困難。
就像我們在 Reply 應用程式中看到的一樣,大螢幕體驗設計並非單純只是為了配合螢幕大小而延展或放大 UI 元素。您必須善加利用更大的可用空間,為使用者打造出不同的使用體驗。舉例來說,您可以在同一個畫面中加入其他版面配置,這樣就不必導覽至另一個畫面,或是必須進行多工處理。
此設計不但可以提升使用者的工作效率,還可以進一步提高參與度。但是在部署此設計之前,您必須先瞭解如何為不同螢幕大小建立不同的版面配置。
6. 配合不同螢幕大小調整版面配置
什麼是中斷點?
您也許會想知道同一個應用程式要如何才能顯示不同的版面配置。簡單來說,就是針對不同的狀態使用條件式,和本程式碼研究室開頭所說的方式一樣。
如要建立可自動調整的應用程式,您必須根據螢幕大小變更版面配置。版面配置變更的測量點稱為中斷點。質感設計建立了一個可自主設計的中斷點範圍,適合大多數的 Android 螢幕使用。
舉例來說,根據此中斷點範圍表,如果您的應用程式目前是在螢幕大小小於 600 dp 的裝置中執行,就應顯示行動裝置版面配置。
使用視窗大小類別
針對 Compose 加入的 WindowSizeClass
API,可簡化質感設計中斷點的實作。
視窗大小類別有三種大小類別:精簡、中型和展開 (寬度和高度)。
如要在 Reply 應用程式中實作 WindowSizeClass
API,請完成下列步驟:
- 新增
material3-window-size-class
依附元件至模組build.gradle
檔案。
build.gradle
...
dependencies {
...
"androidx.compose.material3:material3-window-size-class:$material3_version"
...
- 新增依附元件後,按一下「Sync Now」即可同步處理 Gradle。
保持更新 build.grade
檔案,您就可以建立變數,隨時儲存應用程式視窗的大小。
- 在
MainActivity.kt
檔案的onCreate()
函式中,利用傳遞至參數的this
結構定義指派calculateWindowSizeClass
至名為windowSize
的變數。 - 匯入適當的
calculateWindowSizeClass
套件。
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp()
...
- 此時您會發現請注意,
calculateWindowSizeClass
語法出現紅色底,並顯示紅色的燈泡圖示。按一下windowSize
變數左側的紅色燈泡,然後選取「Opt in for ‘ExperimentalMaterial3WindowSizeClassApi' on ‘onCreate'」,以在onCreate()
方法上方建立註解。
您可以使用 MainActivity.kt
中的 WindowWidthSizeClass
變數,決定要在不同可組合項中顯示的版面配置。現在讓我們來準備 ReplyApp
可組合項以接收此值。
- 在
ReplyApp.kt
檔案中,修改ReplyApp
可組合項以接受WindowWidthSizeClass
做為參數,並匯入適當的套件。
ReplyApp.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
...
- 將
windowSize
變數傳遞至MainActivity.kt
檔案onCreate()
方法中的ReplyApp
元件。
MainActivity.kt
...
setContent {
ReplyTheme {
val windowSize = calculateWindowSizeClass(this)
ReplyApp(
windowSize = windowSize.widthSizeClass
)
...
此外,您也需要更新 windowSize
參數的應用程式預覽。
- 將
WindowWidthSizeClass.Compact
做為windowSize
參數傳遞至預覽元件的ReplyApp
可組合項,然後匯入適當的套件。
MainActivity.kt
...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...
@Preview(showBackground = true)
@Composable
fun ReplyAppPreview() {
ReplyTheme {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact,
)
}
}
- 如要根據螢幕大小變更應用程式版面配置,請根據
WindowWidthSizeClass
值在ReplyApp
可組合項中新增when
陳述式。
ReplyApp.kt
...
@Composable
fun ReplyApp(
windowSize: WindowWidthSizeClass,
modifier: Modifier = Modifier
) {
val viewModel: ReplyViewModel = viewModel()
val replyUiState = viewModel.uiState.collectAsState().value
when (windowSize) {
WindowWidthSizeClass.Compact -> {
}
WindowWidthSizeClass.Medium -> {
}
WindowWidthSizeClass.Expanded -> {
}
else -> {
}
}
...
此時您已經打好基礎,可使用 WindowSizeClass
值變更應用程式的版面配置。下一步是要決定您的應用程式在不同大小螢幕中的呈現效果。
7. 實作自動調整導覽版面配置
實作自動調整 UI 導覽
目前所有螢幕大小的配置都使用底部導覽。
如先前所述,此導覽元素並不理想,因為使用者如果使用較大的螢幕,要觸及這些重要的導覽元素可能會有困難。不用擔心,回應式 UI 導覽功能提供了適合不同視窗大小類別使用的建議模式。在 Reply 應用程式中,您可以實作下列元素:
導覽邊欄是質感設計的另一個導覽元件,可讓使用者從應用程式側邊存取主要目的地的精簡導覽選項。
同樣地,固定式/持續顯示的導覽匣也是由質感設計建立的另一個選項,可為較大的螢幕畫面提供符合人體工學的存取方式。
實作導覽匣
如要建立適合展開畫面使用的導覽匣,您可以使用 navigationType
參數。請執行下列步驟以完成設定:
- 如要表示不同類型的導覽元素,請在
ui
目錄的新套件utils
中建立新檔案WindowStateUtils.kt
。 - 新增
Enum
類別以代表不同類型的導覽元素。
WindowStateUtils.kt
package com.example.reply.ui.utils
enum class ReplyNavigationType {
BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}
如要完成實作導覽匣,您必須根據應用程式的視窗大小決定導覽類型。
- 在
ReplyApp
可組合項中,建立navigationType
變數,並根據when
陳述式中的螢幕大小指派適當的ReplyNavigationType
值。
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyNavigationType
...
when (windowSize) {
WindowWidthSizeClass.Compact -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
WindowWidthSizeClass.Medium -> {
navigationType = ReplyNavigationType.NAVIGATION_RAIL
}
WindowWidthSizeClass.Expanded -> {
navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
}
else -> {
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
}
}
...
您可以在 ReplyHomeScreen
可組合項中使用 navigationType
值。如要執行此操作,請先將其設為可組合項的參數。
- 在
ReplyHomeScreen
可組合項中,新增navigationType
做為參數。
ReplyHomeScreen.kt
...
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
)
...
- 將
navigationType
傳遞至ReplyHomeScreen
可組合項。
ReplyApp.kt
...
ReplyHomeScreen(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
...
接下來,您可以建立分支版本,以便在使用者於展開螢幕中開啟應用程式並顯示主畫面時,顯示有導覽匣的應用程式內容。
- 在
ReplyHomeScreen
可組合項主體中,新增navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage
條件的if
陳述式。
ReplyHomeScreen.kt
import androidx.compose.material3.PermanentNavigationDrawer
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
}
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
...
- 如要建立固定式導覽匣,請在 if 陳述式的主體中建立
PermanentNavigationDrawer
可組合項,然後新增NavigationDrawerContent
可組合項作為drawerContent
參數的輸入內容。 - 將
ReplyAppContent
可組合項新增為PermanentNavigationDrawer
.的最終 lambda引數。
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
}
...
- 新增
else
分支版本,以便使用先前的可組合主體針對非展開式螢幕保留之前的分支版本設定。
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
&& replyUiState.isShowingHomepage
) {
PermanentNavigationDrawer(
drawerContent = {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
}
}
...
- 在
ReplyHomeScreen
可組合項中新增實驗功能註解。由於PermanentNavigationDrawer
API 仍處於實驗階段,因此必須加入註解。
ReplyHomeScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit = {},
onEmailCardPressed: (Email) -> Unit = {},
onDetailScreenBackPressed: () -> Unit = {},
modifier: Modifier = Modifier
) {
...
- 在「平板電腦」模式中執行應用程式。您應該會看到以下畫面:
實作導覽邊欄
與導覽匣實作類似,您必須使用 navigationType
參數在導覽元素之間切換。
首先,我們要為中型螢幕加入導覽邊欄。
- 先新增
navigationType
做為參數,以便做好ReplyAppContent
可組合項的準備。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- 將
navigationType
值傳遞至兩個ReplyAppContent
可組合項。
ReplyHomeScreen.kt
...
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
navigationType = navigationType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
...
接下來,我們要新增分支版本,允許應用程式在某些情況下顯示導覽邊欄。
- 在
ReplyAppContent
可組合項主體第一行中,於AnimatedVisibility
可組合項周圍納入ReplyNavigationRail
可組合項,然後將visibility
參數設為true
(如果ReplyNavigationType
值為NavigationRail
)。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
...
- 如要正確對齊可組合項,請在
Row
可組合項中同時納入在ReplyAppContent
主題中找到的AnimatedVisibility
可組合項和Column
可組合項。
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit) = {},
onEmailCardPressed: (Email) -> Unit = {},
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.fillMaxSize()) {
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
ReplyNavigationRail(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
Column(
modifier = Modifier
.fillMaxSize() .background(MaterialTheme.colorScheme.inverseOnSurface)
) {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
}
...
最後,我們要確保在某些情況下畫面會顯示底部導覽列。
- 在
ReplyListOnlyContent
可組合項之後,使用AnimatedVisibility
可組合項納入ReplyBottomNavigationBar
可組合項。 - 當
ReplyNavigationType
值為BOTTOM_NAVIGATION
時,設定visible
參數。
ReplyHomeScreen.kt
...
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
...
- 在「未折疊的折疊式裝置」模式中執行應用程式。您應該會看到以下畫面:
8. 取得解決方案程式碼
完成程式碼研究室後,如要下載當中用到的程式碼,您可以使用以下 Git 指令:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git cd basic-android-kotlin-compose-training-reply-app git checkout nav-update
另外,您也可以下載存放區為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。
如要查看解決方案程式碼,請前往 GitHub 檢視。
9. 結語
恭喜!您完成了實作自動調整導覽版面配置,現在朝將 Reply 應用程式設為可依照所有螢幕大小自動調整的目標又邁進了一大步。您運用多種 Android 板型規格提升了使用者體驗。在接下來的程式碼研究室中,您將透過實作自動調整內容版面配置、測試和預覽功能,進一步提升自動調整應用程式的運用技巧。
記得使用 #AndroidBasics,透過社群媒體分享您的作品!