本文由 简悦SimpRead 转码,原文地址 getstream.io
了解如何使用Jetpack WindowManager库,并建立一个可折叠的设备模拟器,这样你就可以e......。
在过去的十年里,移动设备的发展迅速扩大了设备形式的生态系统。可折叠设备是最近新增的设备之一,它突破了我们认为可能的界限,根据设备的折叠状态,提供不同的显示屏--甚至是显示屏的组合。
在这新一代的可折叠设备中,双屏设备特别有趣,它们提供对称的屏幕,以独特的方式一起工作。但是,它们也带来了独特的挑战。
这些可折叠、响应式用户界面的一个关键特征是,屏幕尺寸可以在运行时改变。这意味着应用程序应该在运行时识别屏幕的变化,这对于需要专注于业务代码的开发人员来说,可能是一个具有挑战性的功能。
为了绕过这个挑战,谷歌提出了一个新的解决方案:Jetpack WindowManager。现在这个库已经进入RC版本,谷歌鼓励所有的开发者采用Jetpack WindowManager,它具有设备无关的API、测试API和WindowMetrics,这样你就可以轻松地对屏幕变化做出反应。
在这篇文章中,你将学习如何设置可折叠设备模拟器,以及如何使用Jetpack WindowManager库来为Android构建响应式UI。
如需其他帮助,请查看下面的链接。
设置可折叠仿真器
为了开始工作,你需要安装一个可折叠的仿真器。在本教程中,你将使用微软的Surface Duo 2仿真器来运行演示项目。为了运行该模拟器,你将学习如何在Android Studio上安装和运行可折叠模拟器。
要想开始,你可以按照下面资源中的说明进行。
注意:如果你用其他可折叠的仿真器构建,这个项目可能无法在你的仿真器上正常工作。
下载Surface Duo仿真器
你可以按照下面的步骤下载Surface Duo模拟器的图像。
- 进入微软下载中心。
- 点击页面上的下载按钮。
- 选择一个下载选项,如下图所示。你应该选择与你的电脑环境相匹配的选项。
- 点击下一步按钮,开始下载仿真器文件。
安装SDK和仿真器
下载完模拟器后,按照下面的说明将其安装到你的电脑上。
👉 Mac的说明。
- 打开SurfaceDuoEmulator.dmg文件。
- 将 .jar 文件和模拟器文件夹的内容复制到本地硬盘上的一个新位置。(仿真器文件夹的名称不能包含句号)。
- 浏览到你复制模拟器文件的SurfaceDuoEmulator文件夹。
- 通过双击运行 ./run.sh (或在终端上输入),开始安装过程。这可能需要一些时间 - 但在这个过程结束时,你将看到模拟器启动。
👉 窗口说明。
- 解压下载的文件。
-
- 启动安装程序。
- 完成SDK设置后,通过开始菜单链接启动**Surface Duo模拟器。
注意:如果模拟器没有启动,你可能需要更新你的Android SDK安装指针。
在Android Studio上运行Surface Duo 2仿真器
运行Surface Duo 2模拟器后,你会在你的电脑上看到下面的结果。
Surface Duo 2模拟器将自动出现在Android Studio的可用设备列表中,如下图所示。然后,你可以在你的Duo 2模拟器上运行这个演示项目。
运行这个演示项目后,你会看到下面的结果。
现在,你可以在模拟器控制器的虚拟传感器标签上控制设备的折叠状态和程度。
祝贺你 现在你可以在Surface Duo 2模拟器上建立响应式用户界面的项目了。现在让我们深入研究Jetpack WindowManager,看看负责的应用程序是如何工作的。
Jetpack WindowManager
Jetpack WindowManager库使应用程序开发人员有可能支持新的设备外形,实现响应式UI。如果你想在你的项目中使用这个库,请在你的应用程序的build.gradle文件中添加以下依赖项。
dependencies {
implementation "androidx.window:window:1.0.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}
Jetpack WindowManager版本1.0.0包含以下主要功能。
WindowMetricsCalculator。计算一个活动的WindowMetrics的接口。它计算窗口的大小和位置,以MATCH_PARENT的宽度和高度占据区域。
WindowInfoTracker: 提供 "WindowLayoutInfo",它包含了一个窗口的显示特征,作为一个可观察的类型,如Flow或RxJava。
WindowLayoutInfo。包含窗口的显示特征,以区分窗口是否包含了折叠或铰链。
FoldingFeature。使你能够监测可折叠设备的折叠状态以确定设备的姿势。
这篇文章将引导你了解Jetpack WIndowManager的主要功能。
设置UI实例
在深入研究WindowManager API之前,你需要在你的Activity上设置一个示例布局来观察折叠状态和显示配置。
首先,打开activity_main.xml文件并复制粘贴以下代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="40dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/metrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="metrics"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/layoutChanges"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/layoutChanges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="layout changes"
android:textSize="32sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toTopOf="@id/posture"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/metrics" />
<TextView
android:id="@+id/posture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="normal posture"
android:textSize="32sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layoutChanges" />
</androidx.constraintlayout.widget.ConstraintLayout>
接下来,在build.gradle文件中用以下代码启用视图绑定。
android {
buildFeatures {
viewBinding true
}
}
最后,在点击Android Studio的同步按钮后,用下面的例子来初始化视图绑定的布局。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
用WindowMetricsCalculator计算屏幕尺寸
现在,让我们用 "WindowMetricsCalculator "来获取窗口的大小。WindowMetricsCalculator "通过计算MATCH_PARENT的宽度和高度以及允许窗口延伸到切口区域后面的任何标志,找到窗口的最大尺寸和区域位置。
首先,你通过使用getOrCreate()静态方法创建一个WindowMetricsCaculator的实例。
val wmc = WindowMetricsCalculator.getOrCreate()
接下来,你可以得到WindowMetrics,它包含Window的尺寸信息。
val wmc = WindowMetricsCalculator.getOrCreate()
val currentWM = wmc.computeCurrentWindowMetrics(this).bounds.flattenToString()
val maximumWM = wmc.computeMaximumWindowMetrics(this).bounds.flattenToString()
binding.metrics.text = "${currentWM}\n$maximumWM"
构建后,你会得到以下结果。
翻转的:
跨度:
用WindowInfoTracker跟踪窗口
WindowManager API提供了WindowLayoutInfo的跟踪接口,它包含位于窗口内的DisplayFeatures列表。我们可以通过使用windowLayoutInfo(activity)方法来观察窗口布局的变化,该方法提供了WindowLayoutInfo的Flow跨活动重现,代码如下。
WindowInfoTracker.getOrCreate(this@MainActivity)
.windowLayoutInfo(this@MainActivity)
.collect { layoutInfo ->
…
}
windowLayoutInfo(activity)方法返回Flow<WindowLayoutInfo>。我们可以用下面的代码观察我们的Activity中的WindowLayoutInfo。
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
//
// Create a new coroutine since repeatOnLifecycle is a suspend function.
lifecycleScope.launch(Dispatchers.Main) {
// The block passed to repeatOnLifecycle is executed when the lifecycle
// is at least STARTED and is cancelled when the lifecycle is STOPPED.
// It automatically restarts the block when the lifecycle is STARTED again.
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collect from WindowInfoTracker when the lifecycle is STARTED
// and stops collection when the lifecycle is STOPPED
WindowInfoTracker.getOrCreate(this@MainActivity)
.windowLayoutInfo(this@MainActivity)
.collect { layoutInfo ->
// do something
}
}
}
用FoldingFeature构建一个响应式屏幕
WindowManager API为你提供了FoldingFeature,它描述了显示器中的折叠和铰链。它的API提供了对以下设备方法相关的重要信息的访问。
-
state(): 代表可折叠设备的当前姿势状态,如STATE_FLAT和STATE_HALF_OPENED。
-
isSeparating(): 决定一个
折叠功能是否应该将窗口分割成多个物理区域,用户会将其视为逻辑上的分离。 -
orientation(): 如果FoldingFeature的宽度大于高度,返回
FoldingFeature.Orientation.HORIZONTAL,否则返回FoldingFeature.Orientation.VERTICAL。
FoldingFeature提供两种折叠状态。FLAT 和 HALF_OPENED。FLAT意味着姿势状态应该完全平放打开,而HALF_OPENDED意味着两个逻辑屏幕区域应该在30到150度之间折叠,如下图所示。
FoldingFeature包括像铰链方向和上面处理过的姿势状态的信息,所以我们可以用这些值来检查设备是在桌面模式还是在书本模式。
桌面模式
你可以用下面的代码检查设备是否处于桌面模式(铰链半开,水平)。
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
private fun isTableTopMode(foldFeature: FoldingFeature) =
foldFeature.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
书本模式
你可以用下面的代码检查设备是否处于书本模式(铰链垂直的半开状态)。
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
private fun isBookMode(foldFeature: FoldingFeature) =
foldFeature.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
👉你可以在这个帖子GitHub中找到完整的示例代码。
更多信息,你也可以查看下面的参考资料。
在您的应用程序中使用Jetpack WindowManager
让我们来看看你如何在我们的下一个应用程序中使用这些API。在下面的截图中,你会看到使用Jetpack WindowManager的响应式用户界面的真实世界的例子。
可折叠的设备可以根据折叠状态拥有灵活的屏幕尺寸,所以我们可以根据不同屏幕后的特定断点来构建响应式的布局。谷歌的Material Design提出了一些关于内容在不同屏幕上如何重新流动的断点指南。
按照Material Design的指导,我们可以通过下面的代码计算屏幕尺寸并定义折叠状态的断点。
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
sealed class WindowSize(val size: DpSize) {
class Compact(windowDpSize: DpSize) : WindowSize(windowDpSize)
class Medium(windowDpSize: DpSize) : WindowSize(windowDpSize)
class Expanded(windowDpSize: DpSize) : WindowSize(windowDpSize)
}
fun getWindowSizeClass(windowDpSize: DpSize): WindowSize = when {
windowDpSize.width < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
windowDpSize.width < 600.dp -> WindowSize.Compact(windowDpSize)
windowDpSize.width < 840.dp -> WindowSize.Medium(windowDpSize)
else -> WindowSize.Expanded(windowDpSize)
}
如果你使用Jetpack Compose在你的应用程序上构建UI,你可以通过下面的例子观察屏幕尺寸。
/**
* Copyright 2022 Google LLC.
* SPDX-License-Identifier: Apache-2.0
*
* Remembers the [WindowSize] class for the window corresponding to the current window metrics.
*/
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
// Get the size (in pixels) of the window
val windowSize = rememberWindowSize()
// Convert the window size to [Dp]
val windowDpSize = with(LocalDensity.current) {
windowSize.toDpSize()
}
// Calculate the window size class
return getWindowSizeClass(windowDpSize)
}
/**
* Remembers the [Size] in pixels of the window corresponding to the current window metrics.
*/
@Composable
private fun Activity.rememberWindowSize(): Size {
val configuration = LocalConfiguration.current
// WindowMetricsCalculator implicitly depends on the configuration through the activity,
// so re-calculate it upon changes.
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
}
return windowMetrics.bounds.toComposeRect().size
}
rememberWindowSizeClass()在Composition中存储了WindowSize对象,如果屏幕尺寸发生变化,它将被更新。所以你可以通过下面的例子来构建响应式的UI。
MessagingScreen(windowSize = rememberWindowSizeClass())
// Draws different UIs depending on the WindowSize.
@Composable
fun MessagingScreen(windowSize: WindowSize) {
when (windowSize) {
is WindowSize.Expanded -> MessagingScreenExpanded(windowSize)
else -> MessagingScreenRegular()
}
}
👉你可以在这个帖子中找到完整的示例代码在GitHub上。这个真实世界的例子是用Stream的Jetpack Compose API构建的,如果你有进一步的兴趣,可以去看看。
总结
在这篇文章中,你学到了如何设置可折叠的模拟器和Jetpack WindowManager来构建响应式的UI。可折叠设备提供了更大的屏幕,这提供了更多的沉浸式用户体验,提高了生产力。因此,你可以通过为你的Android应用程序支持响应式UI来提供更好的用户体验。
同样,你可以在这篇文章GitHub中找到完整的示例代码与例子。
要了解更多关于可折叠设备以及如何使用它们,请看这篇了解可折叠设备。
如果你对这篇文章有任何反馈,请在Twitter @getstream_io上联系我们的团队或作者
@github_skydoves。
像往常一样,祝你编码愉快!