為任意螢幕尺寸構建 Android 介面

Android開發者發表於2022-01-19

在過去的 12 個月內,有約 1 億臺新平板裝置被啟用,Chrome OS 的啟用量增長了 92%,是增長速度最快的桌面平臺。這意味著在平板電腦、可摺疊裝置和 Chrome OS 裝置上,有超過 2.5 億臺大螢幕裝置執行著 Android 系統,而關於可摺疊裝置的使用數量也在不斷增長,同比增長超過 250%,因此,"大屏" 正在成為 Android 裝置中一個重要且增長勢頭最快的細分市場。這也讓裝置製造廠商們意識到,針對大屏做優化是讓裝置在高階手機細分市場中脫穎而出的機會。

隨著平板和可摺疊裝置的迅速發展,是時候停止將手機和平板分開去考慮了,而更應該提供面向一整個生態系統的應用,來提高其在市場中的影響力。本文我們將介紹開發者如何通過我們提供的新 API 和工具快速擁抱並進入這一細分市場。

如果您更喜歡通過視訊瞭解此內容,請在此處檢視:

https://www.bilibili.com/vide...

△ 為任意螢幕尺寸構建 Android 介面

使用者參與度

在 Android 開發者峰會舉辦後的幾個月,Play 商店推出新的激勵措施,包括會按照裝置型別對應用進行評級等舉措,鼓勵開發者將更多目光放到大屏上去。所以目前正是迎接這些變化的絕佳時機,不僅能夠迎合之後的市場變化,還能就此解決因為沒有適配大屏而造成使用者的使用體驗欠缺的尷尬。

△ 針對大屏優化的 Microsoft Outlook 應用介面

△ 針對大屏優化的 Microsoft Outlook 應用介面

我們還觀察到那些針對所有螢幕尺寸進行優化的應用,在圍繞使用者互動度、留存率等指標上,都取得了不錯的成績。比如其中的一個成功案例 Candy Camera,它通過優化可摺疊裝置和大螢幕的佈局,使得使用這些裝置的使用者在應用上花費的時間增加了 10%,7 天使用者留存率更是增長了 14%,而這並不是個例。另一個案例是 Microsoft Outlook,它最近的更新通過使用雙視窗布局充分發揮了大屏優勢,可以同時檢視收件箱和電子郵件內容,並能夠在擁有多個螢幕的某個單獨視窗中獨立撰寫電子郵件。這些例子充分表明: 是時候開始擺脫手機這一單一介面限制的束縛,從而自由地進行設計和開發了。

但是也別太擔心,我們為此已經做了很多的工作,旨在讓您在整個開發週期中儘可能更輕鬆一些,接下來看看我們提供了哪些幫助您更好進行大屏適配的工具吧。

視窗大小類和 Reference Devices

在多元化的裝置生態中,各種 Android 裝置的形狀各異且尺寸不一,這就使得應用的佈局需要十分靈活。在不同的裝置上執行同一應用,都應該能夠靈活適應不同裝置的螢幕尺寸。為此,我們深入研究了 Android 裝置市場,並從 Web 的自適應和響應式開發的最佳實踐中汲取了一些靈感,構建出可動態調整尺寸的新 Android 介面基礎,我們將其稱為視窗大小類。

視窗大小類是一組主觀的視口斷點,您可以根據它們來設計、開發和測試可調整大小的應用佈局。這些斷點將幫助您瞭解要進行優化的關鍵尺寸,以便將應用適配於整個生態系統。視窗大小類分為三類,分別是較小型、中等型和展開型,它們旨在平衡佈局的簡單和靈活性,以針對特殊情況優化您的應用。我們推薦您使用視窗大小斷點來做出高階應用佈局決策,對於佈局網格列的變化,它們還能對映到 Material Design 佈局斷點。

新的 WindowSizeClass API 會在 Jetpack WindowManager 1.1 中提供,它將讓您擺脫易出錯的 isTable 邏輯。這些新 API 還將消除裝置在橫豎屏切換時需要自定義邏輯的需求,在大多數情況下只需針對不同的視窗大小類斷點進行設計,應用就會適應正確的佈局和各種應用狀態。

class WindowMetrics {
    class WindowSizeClass(val name: String) {
        companion object {
            val COMPACT = WindowSizeClass(“COMPACT”)
            val MEDIUM = WindowSizeClass(“MEDIUM”)
            val EXPANDED = WindowSizeClass(“EXPANDED”)
        }
    }

    val widthClass: WindowSizeClass
        get() {...}
    val heightClass: WindowSizeClass
        get() {...}
}

有一點比較重要的是,從 Android 12 開始,將允許應用任意調整尺寸,且允許所有應用都以多視窗模式執行。以 Samsung Galaxy Fold 系列來看,其提供的分屏模式使得螢幕利用率提高了 7 倍,而分屏允許使用者根據自己的偏好對尺寸進行調整,這也進一步突出了構建可動態調整尺寸介面的重要性。

從裝置和配置的角度來對佈局進行考量,我們讓每個視窗大小類都代表了一些典型裝置的配置 (如下圖所示),當您考慮基於斷點對佈局進行設計時,這將會是一個很有用的參考。其中,較小型代表了豎屏模式下手機的典型模式,中等型代表了大部分平板電腦和更大的可摺疊裝置的尺寸,展開型則代表了平板電腦或更大的可摺疊裝置,或是桌面裝置在橫屏模式下的顯示情況。

△ 基於寬度的視窗大小類的表示

△ 基於寬度的視窗大小類的表示

除了以上三種基於寬度的斷點外,我們還引入了具有相同類別名稱的基於高度的斷點,以便適用於更高階別的佈局場景,並賦予更多的靈活性。假設我們需要使用較小的高度斷點來對橫屏手機介面進行佈局優化,雖然這聽起來很複雜,但是別擔心,根據我們同許多 Android 開發者進行深談後,大部分情況下只需要根據寬度進行佈局適配就可以了。

△ 基於高度的視窗大小類的表示

△ 基於高度的視窗大小類的表示

總而言之,視窗大小類的出現,代表了 Android 在自適應和響應式佈局開發中的一大進步,包括更新和優化的指南、Jetpack WindowManager 中的新 API 以及 Android Studio 中的新工具。

談到 Android Studio,我們將在 Android Studio Bumblebee 中引入一種新的工具類別,我們將其稱為 Reference Devices,它的引入是為了讓 Android 應用的構建能夠響應和適應所有裝置類別。我們在對市場資料進行充分研究之後,提供了四種 Reference Devices,分別代表了手機、可摺疊裝置、平板電腦和桌面裝置。它們既可以覆蓋目前市場上的主流裝置,又涵蓋到了快速增長的細分市場,還可以確保應用在大部分視窗大小類中都能夠正常執行。

△ 四種 Reference Devices

△ 四種 Reference Devices

在本文對大螢幕適配的介紹中,若您只想快速知曉要注意的點,那請記住以下幾點:

  1. 為了確保應用在不同裝置尺寸上都能夠正確展示,請優先針對較小和展開型寬度大小類來優化佈局;
  2. 在所有的 Reference Devices 上都測試一遍您的應用,優先採用在中等型下的最佳佈局;
  3. 為了提供更好的使用者體驗,請新增對應用有意義的功能,如支援可摺疊裝置的摺疊狀態或針對鍵盤、滑鼠和觸控筆輸入支援進行優化;

如需瞭解更多有關視窗大小類的詳細資訊,請查閱 視窗大小類

如需檢視關於視窗大小類的實際應用示例,請參閱 JetNews Compose 示例

適配大屏

設計美觀且響應迅速的介面是開發應用的第一步,但如何實現和維護這種設計絕對是個挑戰,為了簡化您的工作,我們會致力於提供高效的工具。現在便會介紹如何通過新的 Jetpack API 和 Android Studio 功能,來對現有應用進行更新,以針對所有螢幕尺寸進行優化。

我們將會使用 Trackr 作為示例,這是一個開源的任務管理應用,我們最近對該應用進行了更新,使其更好地支援更大螢幕的裝置。Trackr 的開發曾是為了展示如何在 Android 中支援無障礙功能體驗的最佳實踐,隨著最近針對大螢幕的更新,它無疑是一個很好的示例。

△ 更改之前的 Trackr 樣式

△ 更改之前的 Trackr 樣式

上圖是我們進行更改之前的 Trackr 樣式,您會發現不管在什麼裝置或螢幕下,都會有一個單視窗任務列表以及用於導航到歸檔或設定頁面的底部應用欄。Trackr 有幾個主要界頁,包括任務列表、任務詳情、任務建立或編輯頁面。接下來,就讓我們對 Trackr 進行大屏優化。

NavigationRailView

我們正在 Android Studio Chipmunk 中開發一個新的工具 Visual Linting。可以通過它在 Layout Validation 中對介面進行檢查,並顯示一些警告和相關建議。我們使用 Visual Linting 對 Trackr 的佈局進行檢查,來通過工具找出一些潛在的大螢幕顯示的相關問題。我們可以開啟 main_activity 佈局,然後開啟 Layout Validation 工具 (還可以通過 View - Tools Window 路徑找到該選項)。

△ Layout Validation 中對介面進行檢查

△ Layout Validation 中對介面進行檢查

在 Layout Validation 介面,您會發現有一個新的 Reference Devices 的類別,通過它您可以在 Android Studio 中使用新的 Reference Devices 功能。在 Layout Validation 右上角可以發現一個警告圖示,單擊此圖示可以開啟警告視窗,點選每個警告會顯示哪些裝置會受到影響。如上圖所示,我們會發現兩個跟大屏顯示相關的警告: 底部應用欄只推薦用於較小螢幕以及 MaterialTextView 的部分行包含超過 120 個字元。

△ 警告視窗

△ 警告視窗

展開警告可以檢視到 Android Studio 是否提供了修改建議,這裡關於底部應用欄警告的修改建議就是使用 Navigation Rail、抽屜式導航欄,或使用頂部應用欄代替。對於 Trackr,我認為使用導航路由更有建設性。而針對 MaterialTextView 的修改建議是要麼減少 TextView 的寬度,要麼考慮使用多列布局,這裡使用多列布局更適合我們的應用。對於 Trackr,我們將會使用典型的列表加詳情視窗的樣式來解決這些警告,針對有著中等或較大寬度的裝置,我們將使用 NavRail,而非底部應用欄,對於展開型寬度的裝置我們將使用雙視窗布局來展示任務和相關詳情。

我們先來進行第一項優化,使用 NavRail 而非底部應用欄,首先我們要考慮的是導航模型,所幸我們不會更改很多具體的檢視,僅僅只會更改導航方式,因為 NavRail 會一直存在於整個檢視體系中,可以通過它導航到任何其他檢視。為了實現這一模式,我們可以將 Navigation Rail View 新增到 main_activity 佈局中,如下程式碼所示:

// main_activity.xml
<androidX.coordinatorlayout.widget.CorrdinatorLayout
…>
<com.google.android.material.navigationrail.NavigationRailView
    android:id="@+id/navigation_rail"
    android :layout_width="wrap_content"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app: layout_constraintTop_toTopOf="parent"
    app:headerLayout="@layout/navigation_rail_header"
    app:labelVisibilityMode="unlabeled"
    app:menu="@menu/navigation_rail" />
</androidX.coordinatorlayout.widget.CorrdinatorLayout>

在此之前,main_activity 僅由 FragmentContainerView 和 CoordinatorLayout 組成,並通過 NavHostFragment 來託管其他 Fragment。而將 NavigationRailView 放置在 main_activity 佈局級別後,它將在所有檢視中持久存在。儘管如此,我只想要 NavigationRail 用於寬度為 600dp 或者更大的螢幕尺寸,要實現這一點,一個簡單的方法是新增資源限定 (resource-qualified) 的 main_activity 佈局,並在包含 NavHostFragment 的 FragmentContainerView 的同一級別上新增 NavigationRailView:

// w600dp/tasks_fragment.xml
<layout...>
   <data.../>
   <androidx.coordinatorlayout.widget.CoordinatorLayout...>
   <com.google.android.material.appbar.AppBarLayout.../>
   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/tasks_list"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:paddingLeft="@dimen/pane_margin"
       android:paddingRight="@dimen/pane_margin"
       tools:ignore="SpeakableTextPresentCheck"
       tools:listitem="@layout/task_summary"/>
-  <com.google.android.material.bottomappbar.BottomAppBar.../>
-  <com.google.android.material.floatingactionbutton.FloatingActionButton.../>
   </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

我們還需要更新 tasks_fragments.xml,從寬度為 600dp 或更大的螢幕中移除底部應用欄。與實現 NavRail 的方式類似,可以為 tasks_fragments 新增資源限定 (resource-qualified) 的佈局,然後就可以移除底部應用欄和相關的懸浮操作按鈕,其他一切保持不變從而讓任務列表繼續按照預期工作。最後,在設定 NavRail 選單欄的 ID 來匹配現有導航目的檢視的 ID,再在 MainActivity 中為 NavRail 設定 NavController:

<!=-NavRail Menu -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        android:id="@+id/nav_tasks"
        android:icon="@drawable/ic_task" .../>
    <item
        android:id="@+id/nav_archives"
        android:icon="@drawable/ic_archive" .../>
</menu>
// MainActivity NavController
class MainActivity: AppCompatActivity() {
...
    override func onCreate(saveInstanceState: Bundle?) {
    ...
        binding.navigationRail?.apply{
            setupWithNavController(navController)
            setOnItemReselectedListener()
            headerView?.setOnClickListener {
                navController.navigate(R.id.nav_task_edit_graph)
            }
        }
    }
}

這樣就完成了,可以在 Android Studio 檢視顯示是否一切正常,通過在各種 Reference Devices 中來回切換檢視佈局是否按照我們的預期進行。當檢視 Phone Reference Device 時,依然能夠看到底部應用欄,而切換到更大的螢幕後,我們發現它開始使用 NavRail 了,一切按照我們的預期進行。

△ Phone Reference Device 下的效果

△ Phone Reference Device 下的效果

△ Tablet Reference Device 下的效果

△ Tablet Reference Device 下的效果

SlidingPanelLayout

接下來讓我們繼續基於展開型寬度裝置來實現雙視窗檢視佈局。支援這一佈局方式的一個簡單方法是使用 SlidingPaneLayout,它的優勢在於可以輕鬆複用現有的佈局程式碼,以下是目前更新後的導航圖:

△ 更新後的導航圖

△ 更新後的導航圖

我們可以通過 NavigationRailView 導航到應用任意一個頂層佈局,但仍然可以通過選擇介面中某個單項任務而導航到詳情頁面的 Fragment。這種模式在實現 SlidingPanelLayout 時會發生一些變化,我們將新增一個新佈局 TwoPaneTasks 來包含 SlidingPaneLayout,此佈局將同時包含任務列表和詳情的 Fragment。通過這種方式更新應用導航,無論螢幕尺寸如何都能夠擁有相同的導航圖,這意味著調整螢幕尺寸不會產生導航的變化,從而讓使用者感到困惑。

由於任務和詳情都呈現在 SlidingPaneLayout 中的同一個新的 Fragment 中,因此我們為該 Fragment 的導航互動專門新增一個新的子導航層次結構。這樣,當我選擇一項任務並且應用從雙視窗變成單視窗時,該專案將位於導航棧的頂部,並是可見的狀態。

簡單說,我們將使用 SlidingPaneLayout 和 FragmentContainerView 來新增一個新 Fragment 來託管任務和詳情窗格,這樣不必對現有程式碼進行大的重構。

// tasks_two_pane_fragment.xml
<layout...>
   <androidx.slidingpanelayout.widget.SlidingPaneLayout
       android:id="@+id/sliding_pane_layout"...>
       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/list_pane"
           android:name="com.example.android.trackr.ui.tasks.TasksFragment"
           android:layout_width="@dimen/list_pane_width"
           android:layout_height="match_parent"
           android:layout_weight="@dimen/list_pane_weight"
           tools:layout="@layout/tasks_fragment" />
   <androidx.fragment.app.FragmentContainerView
           android:id="@+id/detail_pane"
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:layout_width="@dimen/detail_pane_width"
           android:layout_height="match_parent"
           android:layout_weight="@dimen/detail_pane_weight"
           app:navGraph="@navigation/task_detail"
           tools:layout="@layout/task_detail_fragment" />
   </androidx.slidingpanelayout.widget.SlidingPaneLayout>
</layout>

然後,繼續更新應用的頂層導航層次結構,使新的雙視窗 Fragment 成為應用的起始目的頁面,並從應用的導航圖中移除詳情目的頁面。

<!-- 頂層導航圖 -->
<navigation app: startDestination="@+id/nav_tasks"...>
   <fragment
       android:id="@+id/nav_tasks"
       android:name="..trackr.ui.tasks.TasksTwoPaneFragment" ...>
       <action
           android:id="@+id/to_task_edit"
           app: destination="@id/nav_task_edit_graph' />
   </fragment>
<!--Remove the 'details' destination-->
...
</navigation>

<!-SlidingPaneLayout 導航圖-->
<navigation...
       app:startDestination="@id/nav_task_detail_placeholder">
       <action
           android:id="@+id/to_task_detail"
           app:destination="@id/nav_task_detail"
           app:popUpTo="@id/nav_task_detail"
           app :popUpToInclusive="true"
       <fragment
           android:id="@+id/nav_task_detail"
           android:name="..trackr.ui..TaskDetailFragment"...>
       <argument
           android:name="taskId"
           app:argType="long" />
   </fragment>
<!-- 為其實目的頁面使用一個 placeholder-->
   <fragment
       android:id="@+id/nav_task_detail_placeholder"
       android:name="..trackr.ui.PlaceholderFragment"
       tools:layout="@layout/placeholder_fragment"/>
</navigation>

最後,再為 SlidingPaneLayout 專門新增一個新導航圖,並在 TasksTwoPaneFragment Kotlin 程式碼中處理 SlidingPaneLayoutNavController 配置邏輯。通過這兩項更改應用在不同裝置不同外形下的佈局會更加合理。完成這些後,我們再次通過在 Android Studio 中的 Reference Devices 工具,就能看到新的佈局在所有的裝置螢幕中都能夠完美佈局了。而為了在應用執行時進行測試,Android Studio Chipmunk 提供了可支援尺寸調整的模擬器,通過它可以在相同的 Reference Devices 之間切換,來快速驗證應用佈局是否正確。

另外,SlidingPaneLayout 提供了另一個重要特性是它不僅適用於大螢幕裝置,而且適用於多螢幕裝置。Microsoft 最近為 SlidingPaneLayout 提供了一個支援鉸鏈檢測的功能,讓其自動能夠支援跨螢幕拆分視窗,而無需更改任何程式碼。這意味著應用的新列表/詳情佈局將適用於所有裝置,包括多螢幕裝置。

雖然上述提到的方法對於優化大屏顯示非常有用,但是許多開發者的應用都基於多個 Activity,對於這些應用,12L 中釋出的新 Activity Embedding API 將使支援雙視窗檢視等新介面正規化變得容易,敬請期待。

Jetpack Compose

Jetpack Compose 在 2021 年 7 月釋出了 1.0 版本後,在 Android 開發者社群產生了巨大反響,成千上萬的應用已經在生產環境中使用了 Compose,包括 Play 商店應用本身。Jetpack Compose 本身是一種宣告式的介面工具包,通過它您可以根據頁面狀態進行描述,Compose 會自行進行所有必要的更新。所有的介面都是通過在程式碼中描述而成,這樣也就很容易在執行時做出關於介面樣式的決策,而在傳統的檢視系統中,我們通過對不同螢幕配置進行編譯,從而實現對檢視的配置,這兩者有著巨大的不同。這也讓 Compose 可以輕鬆解決不同螢幕尺寸而帶來的介面更改。

接下來,讓我們通過 JetNews 來向您展示如何通過 Compose 來進行不同螢幕尺寸的適配。JetNews 的主介面展示了一長串滾動的文章,在針對大螢幕進行優化之前,它的介面如下圖所示,可以發現,並沒有很好地利用額外的螢幕空間。

△ JetNews 的主介面展示

△ JetNews 的主介面展示

前文中已經介紹了 WindowManager API,目前我們正在將其整合到 Compose 中去,以便更輕鬆地從 Compose 中訪問這些資訊。在此期間,我們可以建立一個 composable 函式來處理與 WindowManager 的整合,然後輕鬆將當前 Activity 的視窗資訊轉換為最終的視窗大小類,程式碼如下所示:

@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
   val configuration = LocalConfiguration.current
   val windowMetrics = remember (configuration) {
       WindowMetricsCalculator.getOrCreate()
           .computeCurrentWindowMetrics(this)
   }
   val windowDpSize = with (LocalDensity.current) {
       windowMetrics.bounds.toComposeRect().size.toDpSize()
   }
   when {
       windowDpSize.width < 600.dp -> WindowSize.Compact
       windowDpSize.width < 840.dp
       else -> WindowSize.Expanded
   }
}

WindowManager 庫很快就會推出直接使用這些類的 API,Compose 也會很快支援更方便的功能來完成此項工作,敬請期待。目前,您可暫時借用這一程式碼來完成這一功能上的需要。

△ JetNews 側邊抽屜導航欄展示

△ JetNews 側邊抽屜導航欄展示

回到 JetNews,我們可以看到在大屏狀態下,側邊的抽屜導航欄會以模態的方式出現,但它會延伸到整個螢幕而出現大量空白區域。根據前文中提到的修改建議,是使用 Navigation Rail,而 Compose 則直接支援,我們僅需要對其進行設定並將內容傳入即可。

NavigationRail(
    header = {
        JetnewsIcon()
    }
) {
    Column(verticalArrangement = Arrangement.Center) {
        Icon(
            icon = Icons.Filled.Home,
            action = navigateToHome
        )
        Icon(
            icon = Icons.Filled.ListAlt,
            action = navigateToInterests
        )
    }
}

標題圖示和兩個導航項圖示,一個用於主頁面,一個用於 Interests 頁面,並新增它們對應的導航操作。為了將 Navigation Rail 整合到應用中,我們對頂層應用元件做了一些更改。首先,我們獲取當前的視窗大小類,以及顯示較小尺寸上的 ModalDrawer,然後確保設定了 ModalDrawer 讓其只響應該尺寸中的手勢。再將 Navigation Rail 與包含應用中所有螢幕的主導航圖並排放置:

@Composable
fun JetnewsApp() {
    val windowSize = rememberWindowSizeState()
    val isDrawerActive = windowSize == WindowSize.Compact
    ModalDrawer(
        gesturesEnabled = isDrawerActive
        drawerContent = {...}
    ) {
        val showNavRail = isDrawerActive
        Row() {
            if (showNavRail) {
                AppNavRail()
            }
            JetnewsNavGraph()
        }
    }
}

然後我們發現由於文章列表依然在大屏下沒有充分利用空間,因此我們決定在大屏下構建列表/詳情佈局,這一佈局方式是 Material Design 中推薦的大螢幕規範佈局之一,讓我們將文章列表與開啟的文章並排顯示。JetNews 應用有兩個我們可以複用的元件: PostList 和 PostContent,這種在一開始就將介面拆分為元件的做法,不僅能讓測試更加容易,還能讓我們輕鬆對佈局進行改進。

為了並排顯示 Feed 和 Post,JetNews 簡單地使用 Row 包裹兩個元件,第一個元件具有固定寬度,第二個元件填充螢幕的其餘部分。詳情元件包裹在交叉漸變動畫中,這讓使用者點選列表開啟文章時看到帶有動畫過渡的轉換效果。

要正確構建列表/詳情結構,除了實際佈局之外我們還需要解決幾個問題。其中比較有趣的一點是思考應用如何在不同尺寸佈局之間轉換,例如對於可摺疊手機,應用可能會從較大的螢幕變為較小的螢幕。

△ 可摺疊手機上佈局轉換

△ 可摺疊手機上佈局轉換

為了正確處理如何將列表和詳情視窗摺疊成單視窗層次結構,當在較小的螢幕上時,我們需要知道使用者最後與哪個視窗互動,為此,我們實現了一個簡單的自定義修飾符來記錄最後一次互動,並以此決定,在不同的摺疊狀態下應該顯示什麼內容,從而進一步提升層次結構。

@Composable
fun HomeFeedWithArticleDetailsScreen(...) {
    Row() {
        PostList(
            modifier
                .width(334.dp)
                .notifyInput(onInteractWithList))
        Crossfade(...) {
            PostContent(
                modifier
                    .fillMaxSize()
                    .notifyInput {
                        onInteractWithDetail(detailPost.id)
                    }
            )
        }
    )}
}

我們還需要知道,我們是從多大尺寸的螢幕將一次只顯示其中一個視窗轉變為顯示列表/詳情佈局的。在 JetNews 中我們首先獲取視窗大小類的資訊,在較小和中等型寬度顯示單視窗,而在展開型寬度顯示列表/詳情佈局。

val windowSize = rememberWindowSizeState()

val homeScreenType = when (windowSize) {
    WindowSize.Compact,
    WindowSize.Medium -> HomeScreenType.Feed
    WIndowSize.Expanded -> HomeScreenType.FeeWithArticleDetails
}

然後,開始針對 JetNews 的導航進行更改。JetNews 最初以主頁面和文章頁面構建而成,每個頁面都有自己的 ViewModel,導航和 ViewModel 之間的整合意味著兩個頁面始終在不同的導航路徑上。但是,為了將頁面重組成列表/詳情佈局,我們需要將這兩個螢幕並排顯示,此處我們有兩種可選方案。一是在詳情頁面巢狀 NavHost,另外一種方案是統一 ViewModel,由於詳情頁面內並沒有下一級別的導航入口而只會顯示一篇開啟的文章,我們決定採用第二種方式,將兩個 ViewModel 合二為一來簡化結構。

我們建立了三個主介面入口點,一個是 HomeFeedScreen,它只負責展示 PostList;一個是 ArticleScreen 負責展示 PostContent;以及新的 HomeFeedWithArticleDetailsScreen 負責顯示包含 PostList 和 PostContent 的列表/詳情佈局。

△ 圖左: 主介面入口點 HomeFeedScreen 圖右: 主介面入口點 ArticleScreen

△ 圖左: 主介面入口點 HomeFeedScreen 圖右: 主介面入口點 ArticleScreen

△ 主介面入口點 HomeFeedWithArticleDetailsScreen

△ 主介面入口點 HomeFeedWithArticleDetailsScreen

上圖是我們適配之前和適配之後的頁面樣式,可以發現對螢幕空間的利用有了非常大的改善。但這次更改是針對螢幕尺寸做的決策,我們是不是可以讓單個元件自身根據頁面而擁有不同尺寸呢?例如我們有一張卡片,當在列表中因為空間的限制只展示標題和副標題,而有更多空間時,則調整為顯示影像。對於此類情況我們可以使用 Box With Constraints,它類似於框佈局,能夠根據範圍內的測量資訊來用於決策。

獲取更好的使用者體驗

在前文中,我們提到為了提供更好的使用者體驗,請新增對應用有意義的功能,如支援可摺疊裝置。同 WindowManager API 類似,我們可以輕鬆地將 Compose 與針對可摺疊裝置的 API 進行整合。通過這些 API,能夠獲取到該裝置是否且何時觸發了鉸鏈或摺疊等功能,以及當前裝置處於何種姿態。Compose 可以輕鬆觀察這些 API 賦予的狀態,從而輕鬆對介面進行轉換。同樣,關於此功能的 API 即將在 Compose 中提供,敬請期待。

除了目前提到的 API 之外,我們一直努力開發 Compose 的內部構件,以增強包括鍵盤和滑鼠支援在內的輸入裝置,這對於在 Chrome OS 上執行的應用尤其有用。如需瞭解 Chrome OS 和輸入詳細資訊,敬請關注我們近期的文章釋出。

如需瞭解更多 Compose 示例詳情,請查閱 Compose 示例程式碼

新的 Compose 和大螢幕指南——構建自適應佈局,希望能夠對您的開發有所幫助。

測試和維護

現在您已瞭解如何輕鬆更新應用,來構建可調整尺寸的新介面。如何測試和維護專案也是一個非常重要的課題。維護並支援所有不同尺寸的介面會大大引入測試複雜性,我們一直努力在不提高工作量的情況下,通過新的自動化測試工具和 API,讓您能夠配置更多裝置來增加測試覆蓋率。我們將會通過 Gradle 託管裝置,從而實現在各種螢幕尺寸和 API 級別上執行虛擬裝置來執行現有的 instrumentation 測試。您只需描述要在其上執行測試的裝置的配置,其餘均由 Gradle 負責,包括裝置預先配置和測試工作的執行。

只需在構建指令碼過程中定義裝置,並將其新增到裝置組:

testOptions
    devices {
        pixel2api29 (com.android.build.api.dsl.ManagedVirtualDevice) {
        nexus9api30 (com.android.build.api.dsl.ManagedVirtualDevice) {
            device = "Nexus 9"
            apiLevel = 30
            systemImageSource = "google"
            abi = "×86"
        }
    }
    deviceGroups {
        mediumAndExpandedWidth{
            targetDevices.addAll(devices.pixel2api29)
            targetDevices.addAll(devices.nexus9api30)
    }
}

然後使用 Gradle 託管裝置組來執行測試:

$ gradlew -Pandroid.experimental.androidTest.useUnifiedTestPlatform=true mediumAndExpandedWidthGroupDebugAndroidTest

由於 Gradle 同時管理裝置配置和測試作業,Gradle 託管裝置還支援測試分片,讓您能夠跨指定數量的相同裝置來分割測試從而減少總體測試作業時間。只需要指定以下引數即可指定要分片的數量:

$ gradlew -Pandroid.experimental.androidTest.numManagedDeviceShards=2 deviceDebugAndroidTest

但我們知道執行大量虛擬裝置會佔用 CPU 和記憶體,這可能會限制 Gradle 託管裝置和測試分片的用處。為了解決此問題,Gradle 託管裝置引入了一種針對 instrumentation 測試而優化的新型虛擬裝置,稱為自動化測試裝置,這些裝置以 headless 模式執行,禁用了自動化測試通常不需要的後臺程式和服務,從而降低了每臺裝置的總體 CPU 和記憶體使用率,這將讓您能夠同時針對代表不同螢幕尺寸的多臺裝置執行測試。當前,這一功能可在 Android 10 上使用,隨著時間的推移將支援更高的 API 級別,以確保現有的螢幕截圖測試能夠繼續與自動化測試裝置配合執行。

我們還在開發一組全新 AndroidX Testing API,讓您能夠將裝置置於不同的狀態進行測試。例如,您可以測試應用從平折變為半開狀態,或在縱向或橫向模式之間旋轉時的反應。

總結

今天我們討論了很多內容,從新的設計指南和視窗大小類,到用於更新現有應用的特定 API。大螢幕和可摺疊裝置代表 Android 的一個龐大且不斷增長的細分市場,為了抓住這一增長機會,現在是時候為這些裝置構建和設計介面,以便為使用最高階裝置的使用者獲得出色的體驗。

歡迎您 點選這裡 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支援!

相關文章