為可折疊設(shè)備和大屏設(shè)備優(yōu)化您的應(yīng)用
Android 設(shè)備的屏幕尺寸日新月異,隨著平板和可折疊設(shè)備的普及度越來越高,在開發(fā)響應(yīng)式用戶界面時,了解您應(yīng)用的窗口尺寸和狀態(tài)顯得尤為重要。Jetpack WindowManager 現(xiàn)已進(jìn)入 beta 測試階段,這個庫提供了與 Android 框架中 WindowManager 比較相似的功能,包括了對支持響應(yīng)式 UI、檢測屏幕改變的回調(diào)適配器和測試窗口 API 的支持。但 Jetpack WindowManager 還新增了對可折疊設(shè)備和 ChromeOS 這類窗口環(huán)境的支持。
新的 WindowManager API 包含了以下內(nèi)容:
Jetpack WindowManager 不與 Android 綁定,這讓 API 能夠迅速地迭代以支持快速發(fā)展的市場,還讓開發(fā)者們能夠通過更新庫而不必等待 Android 版本更新來獲得支持。
現(xiàn)在,Jetpack WindowManager 庫已進(jìn)入 beta 測試階段,我們鼓勵所有開發(fā)者來使用 Jetpack WindowManager,其與設(shè)備無關(guān) API、測試 API 以及它引入的 WindowMetrics,使您的應(yīng)用能夠輕松響應(yīng)窗口尺寸的變化。已經(jīng)進(jìn)入 beta 測試階段,意味著您可以安心地專注于在這些設(shè)備上打造激動人心的體驗(yàn),Jetpack WindowManager 蕞低支持到 API 14。
關(guān)于 Jetpack WindowManager
Jetpack WindowManager 是一個以 Kotlin 優(yōu)先的現(xiàn)代化庫,它支持不同形態(tài)的新設(shè)備,并提供 "類 AppCompat" 的功能以構(gòu)建具有響應(yīng)式 UI 的應(yīng)用。
折疊狀態(tài)
支持可折疊設(shè)備是 Jetpack WindowManager 庫蕞直觀的功能。當(dāng)設(shè)備的折疊狀態(tài)變化時,應(yīng)用將收到相應(yīng)的事件,進(jìn)而更新 UI 界面以支持新的用戶交互。
△ 在 Samsung Galaxy Z Fold2 上運(yùn)行的 Google Duo
您可以通過 Google Duo 學(xué)習(xí)案例 來了解如何支持可折疊設(shè)備。
折疊狀態(tài)有兩種,分別是 FLAT (展平) 和 HALF_OPENED (半開)。對于 FLAT,您可以認(rèn)為表面是完全平整打開的,盡管有些情況下它有可能被鉸鏈分割。對于 HALF_OPENED,窗口中有至少兩個邏輯區(qū)域。我們在下方用圖片說明了每種狀態(tài)各自可能的情況。
△ 折疊狀態(tài): FLAT 和 HALF-OPENED
在應(yīng)用活躍的狀態(tài)下,可以通過 Kotlin 數(shù)據(jù)流收集事件來獲得折疊狀態(tài)改變的信息。
我們通過 lifecycleScope 來控制事件收集的開始和結(jié)束,正如文章《設(shè)計(jì) repeatonLifeCycle API 背后的故事》和示例代碼所述:
lifecycleScope.launch(Dispatchers.Main) { // 傳遞給 repeatonLifecycle 的代碼塊將在生命周期進(jìn)入 STARTED 時執(zhí)行 // 并在生命周期為 STOPPED 時取消 // repeatonLifecycle 將會在生命周期再次進(jìn)入 STARTED 時自動重啟代碼塊 lifecycle.repeatonLifecycle(Lifecycle.State.STARTED) { // 當(dāng)生命周期處于 STARTED 時安全地從 windowInfoRepository 中收集數(shù)據(jù) // 當(dāng)生命周期進(jìn)入 STOPPED 時停止收集數(shù)據(jù) windowInfoRepository.windowLayoutInfo .collect { newLayoutInfo -> updateStateLog(newLayoutInfo) updateCurrentState(newLayoutInfo) } }}
當(dāng)用戶可以看到應(yīng)用時,應(yīng)用可以使用其接收到的 WindowLayoutInfo 對象中包含的信息更新布局。
FoldingFeature 包括了諸如鉸鏈 方向,及折疊功能是否創(chuàng)建了兩個邏輯屏幕區(qū)域 (isSeparating 屬性) 這類信息。我們能使用這些值來檢查設(shè)備是否處于桌面模式 (屏幕半開并且鉸鏈處于水平方向):
△ 設(shè)備處于 TableTop 模式
private fun isTableTopMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
或者書本模式 (屏幕半開并且鉸鏈處于垂直方向):
△ 設(shè)備處于 Book 模式
private fun isBookMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
請參閱: 可折疊設(shè)備中的桌面模式,文內(nèi)示例介紹了如何在媒體播放器應(yīng)用中實(shí)現(xiàn)這樣的功能。
注意: 在主線程/UI 線程中收集事件這點(diǎn)十分重要,這能避免在 UI 和事件處理之間的同步問題。
支持響應(yīng)式 UI
Android 設(shè)備的屏幕尺寸變化十分頻繁,因此著手設(shè)計(jì)能夠完全自適應(yīng)和響應(yīng)式的 UI 非常重要。Jetpack WindowManager 庫中包含的另一個功能——能夠檢索當(dāng)前窗口和蕞大窗口的指標(biāo)信息。這和 API 30 當(dāng)中的 WindowMetrics API 類似,但它向后兼容到 API 14。
Jetpack WindowManager 提供了兩種途徑來檢索 WindowMetrics 信息,通過數(shù)據(jù)流事件中的流或者通過 WindowMetricsCalculator 類進(jìn)行同步處理。
當(dāng)在編寫視圖代碼時,使用異步 API 可能比較困難 (比如 onMeasure),此時可以使用 WindowMetricsCalculator。
val windowMetrics = WindowMetricsCalculator.getOrCreate()puteCurrentWindowMetrics(activity)
另一個使用場景是用于測試中 (詳見下面的測試一節(jié))。
在處理應(yīng)用 UI 的高階用法中,通過該庫提供的 WindowInfoRepository#currentWindowMetrics 能夠在窗口尺寸變更時收到通知,這與是否觸發(fā)配置變更無關(guān)。
這個例子是關(guān)于如何根據(jù)可用區(qū)域來切換您的布局:
// 因?yàn)?repeatonLifecycle 是掛起函數(shù),所以創(chuàng)建一個新的協(xié)程lifecycleScope.launch(Dispatchers.Main) { // 傳遞給 repeatonLifecycle 的代碼塊將在生命周期進(jìn)入 STARTED 時執(zhí)行 // 并在生命周期為 STOPPED 時取消 // 它將會在生命周期再次進(jìn)入 STARTED 時自動重啟 lifecycle.repeatonLifecycle(Lifecycle.State.STARTED) { // 當(dāng)生命周期處于 STARTED 時安全地從 windowInfoRepository 中收集數(shù)據(jù) // 當(dāng)生命周期進(jìn)入 STOPPED 時停止收集數(shù)據(jù) windowInfoRepository.currentWindowMetrics .collect { windowMetrics -> val currentBounds = windowMetrics.bounds Log.i(TAG, "New bounds: {$currentBounds}") // 我們可以根據(jù)需要在這里更新布局 } }}
回調(diào)適配器
要在 Java 編程語言中使用這個庫或者使用回調(diào)接口,請?jiān)谀膽?yīng)用中添加 androidx.window:window-java依賴。該組件提供了 WindowInfoRepositoryCallbackAdapter,您可以通過它注冊 (取消注冊) 一個用以接收設(shè)備姿態(tài)及窗口指標(biāo)信息更新的回調(diào)。
public class SplitLayoutActivity extends AppCompatActivity { private WindowInfoRepositoryCallbackAdapter windowInfoRepository; private ActivitySplitLayoutBinding binding; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); 等Override protected void onCreate(等Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); windowInfoRepository = new WindowInfoRepositoryCallbackAdapter(WindowInfoRepository.getOrCreate(this)); } 等Override protected void onStart() { super.onStart(); windowInfoRepository.addWindowLayoutInfoListener(Runnable::run, layoutStateChangeCallback); } 等Override protected void onStop() { super.onStop(); windowInfoRepository.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { 等Override public void accept(WindowLayoutInfo windowLayoutInfo) { binding.splitLayout.updateWindowLayout(windowLayoutInfo); } }}
測試
開發(fā)者們講到,更健壯的測試 API 對于維護(hù) LTS (長期支持) 是十分關(guān)鍵的。讓我們來聊聊如何在普通設(shè)備上測試可折疊設(shè)備姿態(tài)。
現(xiàn)在,我們已經(jīng)知道 Jetpack WindowManager 庫可以在設(shè)備姿態(tài)改變時,向您的應(yīng)用發(fā)送通知,以便您修改應(yīng)用的布局。
該庫在 androidx.window:window-testing 中提供了 WindowLayoutInfoPublisherRule 讓您能夠發(fā)布一個 WindowInfoLayout 以支持測試 FoldingFeature:
import androidx.window.testing.layout.FoldingFeatureimport androidx.window.testing.layout.WindowLayoutInfoPublisherRule
我們可以在測試中虛擬一個 FoldingFeature:
val feature = FoldingFeature( activity = activity, center = center, size = 0, orientation = VERTICAL, state = HALF_OPENED)val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build()publisherRule.overrideWindowLayoutInfo(expected)
然后使用 WindowLayoutInfoPublisherRule 來發(fā)布它:
val publisherRule = WindowLayoutInfoPublisherRule()publisherRule.overrideWindowLayoutInfo(expected)
蕞后,使用可用的 Espresso 匹配器 來檢查我們正在測試的 Activity 的布局是否符合預(yù)期。
下面這個測試中發(fā)布了一個處于 HALF_OPENED 狀態(tài)并且鉸鏈垂直于屏幕中心的 FoldingFeature:
等Testfun testDeviceOpen_Vertical(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity -> val feature = FoldingFeature( activity = activity, orientation = VERTICAL, state = HALF_OPENED ) val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() val value = testScope.async { activity.windowInfoRepository().windowLayoutInfo.first() } publisherRule.overrideWindowLayoutInfo(expected) runBlockingTest { Assert.assertEquals( expected, value.await() ) } } // 檢查在有垂直折疊特性時 start_layout 在 end_layout 的左側(cè) // 這需要在足夠大的屏幕上運(yùn)行測試以適應(yīng)屏幕上的兩個視圖 onView(withId(R.id.start_layout)) .check(isCompletelyLeftOf(withId(R.id.end_layout)))}
查看示例代碼
Github 上的 蕞新示例 展示了如何使用 Jetpack WindowManager 庫從 WindowLayoutInfo 流收集信息,或者通過向 WindowInfoRepositoryCallbackAdapter 注冊回調(diào)來獲取顯示姿態(tài)信息。
該實(shí)例還包含一些測試,它們可以在任何設(shè)備或模擬器中運(yùn)行。
在您的應(yīng)用中使用 WindowManager
可折疊設(shè)備及雙屏設(shè)備不再僅僅是實(shí)驗(yàn)性的或前瞻的——大屏幕空間和額外的設(shè)備姿態(tài)已經(jīng)被證實(shí)是具有用戶價值的,而且現(xiàn)在有更多的設(shè)備可供您的用戶選擇。可折疊設(shè)備和雙屏設(shè)備代表了智能手機(jī)的自然進(jìn)化。對于 Android 開發(fā)者來說,這提供了一個進(jìn)入正在增長的高端市場的機(jī)會,感謝設(shè)備制造商們重新開始關(guān)注大屏設(shè)備。
我們?nèi)ツ晖瞥隽?Jetpack WindowManager alpha01 版本。該庫自那時起開始穩(wěn)步地發(fā)展,早期的反饋?zhàn)屍溆辛撕艽蟮母倪M(jìn)。現(xiàn)在,它已經(jīng)擁抱了 Android 的 Kotlin 優(yōu)先理念,從回調(diào)驅(qū)動模型逐漸過渡到協(xié)程和數(shù)據(jù)流。隨著 WindowManager 進(jìn)入測試階段,API 已經(jīng)穩(wěn)定,我們強(qiáng)烈建議使用它。
更新并不僅限于此。我們計(jì)劃為該庫添加更多功能,并使其發(fā)展成為與 AppCompat 解綁的系統(tǒng) UI 庫,使開發(fā)者能夠在所有的 Android 設(shè)備上輕松實(shí)現(xiàn)現(xiàn)代化的、響應(yīng)式的 UI。
歡迎您 點(diǎn)擊這里 向我們提交反饋,或分享您喜歡的內(nèi)容、發(fā)現(xiàn)的問題。您的反饋對我們非常重要,感謝您的支持!