1. 准备工作
可折叠设备有什么特别之处?
可折叠设备是一项卓越的创新。它们提供独特的体验,并带来独特的机会,通过免触摸使用的桌面界面等差异化功能满足用户的需求。
前提条件
- 具备开发 Android 应用的基础知识
- 了解有关 Hilt 依赖项注入框架的基础知识
构建内容
在此 Codelab 中,您将针对可折叠设备构建具有优化布局的相机应用。
您会从一个基本相机应用入手,该应用不会响应任何设备折叠状态,也没有使用更好的后置摄像头来提高自拍效果。您需要更新源代码,以便在设备展开时将预览移至较小的显示屏,并对设置为桌面模式的手机做出反应。
虽然相机应用是此 API 最便捷的用例,但您在此 Codelab 中学到的这两项功能都可以应用于任何应用。
学习内容
- 如何使用 Jetpack Window Manager 对折叠状态变化做出反应
- 如何将应用移至可折叠设备较小的显示屏
所需条件
- 最新版本的 Android Studio
- 可折叠设备或可折叠模拟器
2. 进行设置
获取起始代码
- 如果您已安装 Git,只需运行以下命令即可。如需检查是否已安装 Git,请在终端或命令行中输入
git --version
,并验证其是否正确执行。
git clone https://github.com/android/large-screen-codelabs.git
打开第一个模块
- 在 Android Studio 中,打开
/step1
下的第一个模块。
如果系统要求您使用最新版 Gradle,请进行更新。
3. 运行并观察
- 在模块
step1
上运行代码。
如您所见,这是一个简单的相机应用。您可以在前置摄像头和后置摄像头之间切换,还可以调整宽高比。但是,左侧第一个按钮当前不会执行任何操作,但它将成为后自拍模式的入口点。
- 现在,尝试将设备置于半开位置,合页并不完全平展或合上,而是形成 90 度角。
如您所见,应用不会响应不同的设备折叠状态,因此布局不会改变,导致合页位于取景器的中间。
4. 了解 Jetpack WindowManager
Jetpack WindowManager 库可帮助应用开发者为可折叠设备打造经过优化的体验。它包含 FoldingFeature
类,该类描述灵活显示屏中的折叠边或两个物理显示面板之间的合页。您可通过其 API 访问与设备相关的重要信息:
- 如果合页以 180 度打开,
state()
会返回FLAT
;否则,会返回HALF_OPENED
。 - 如果
FoldingFeature
宽度大于高度,orientation()
将返回FoldingFeature.Orientation.HORIZONTAL
;否则,返回FoldingFeature.Orientation.VERTICAL
。 bounds()
以Rect
格式提供FoldingFeature
的边界。
FoldingFeature
类包含更多信息(例如 occlusionType()
或 isSeparating()
),但此 Codelab 不会深入探讨这些信息。
从 1.1.0-beta01 版本开始,该库使用 WindowAreaController
,该 API 使后置显示模式能够将当前窗口移至与后置摄像头对齐的显示屏,非常适合使用后置摄像头自拍,以及许多其他使用场景!
添加依赖项
- 如需在应用中使用 Jetpack WindowManager,您需要将以下依赖项添加到模块级
build.gradle
文件中:
step1/build.gradle
def work_version = '1.1.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"
现在,您可以访问应用中的 FoldingFeature
和 WindowAreaController
类。您可以使用它们打造卓越的可折叠相机体验!
5. 实现后置自拍模式
先从后置显示屏模式开始。允许此模式的 API 是 WindowAreaControllerJavaAdapter
,它需要 Executor
并返回存储当前状态的 WindowAreaSession
。在 Activity
被销毁并重新创建后,应保留此 WindowAreaSession
,以便您将其存储在 ViewModel
中,从而在配置发生变化时安全地进行存储。
- 在
MainActivity
中声明以下变量:
step1/MainActivity.kt
private lateinit var windowAreaController: WindowAreaControllerJavaAdapter
private lateinit var displayExecutor: Executor
- 然后,在
onCreate()
方法中初始化这些变量:
step1/MainActivity.kt
windowInfoTracker = WindowInfoTracker.getOrCreate(this)
displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaControllerJavaAdapter(WindowAreaController.getOrCreate())
现在,您的 Activity
已准备好将内容移动到较小的显示屏上,但您需要存储会话。
- 如需存储会话,请打开
CameraViewModel
并在其中声明此变量:
step1/CameraViewModel.kt
var rearDisplaySession: WindowAreaSession? = null
private set
您需要将 rearDisplaySession
作为变量,因为它会在您每次创建时发生变化,但您希望确保它不能从外部更新,因为您现在创建了一个在需要时更新它的方法。
- 将以下代码粘贴到
CameraViewModel
中:
step1/CameraViewModel.kt
fun updateSession(newSession: WindowAreaSession? = null) {
rearDisplaySession = newSession
}
每当代码需要更新会话时,系统都会调用此方法,将此方法封装到单个接入点会很有帮助。
Rear Display API 可与监听器方法搭配使用:当您请求将内容移至较小的屏幕时,系统会启动通过监听器的 onSessionStarted()
方法返回的会话。如果您想返回内部(较大)显示屏,需要关闭会话并在 onSessionEnded()
方法中收到确认信息。您会利用这些方法以更新 CameraViewModel
内的 rearDisplaySession
。如需创建此类监听器,您需要实现 WindowAreaSessionCallback
接口。
- 修改
MainActivity
声明,使其实现WindowAreaSessionCallback
接口:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
现在,在 MainActivity
中实现 onSessionStarted
和 onSessionEnded
方法。对于第一个方法,您需要保存 WindowAreaSession
,并在第二个方法中将其重置为 null
。这尤为有用,因为您可以使用 WindowAreaSession
来决定是启动会话还是关闭现有会话。
step1/MainActivity.kt
override fun onSessionEnded() {
viewModel.updateSession(null)
}
override fun onSessionStarted(session: WindowAreaSession) {
viewModel.updateSession(session)
}
- 在
MainActivity.kt
文件中,编写此 API 正常运行所需的最后一段代码:
step1/MainActivity.kt
private fun startRearDisplayMode() {
if (viewModel.rearDisplaySession != null) {
viewModel.rearDisplaySession?.close()
} else {
windowAreaController.startRearDisplayModeSession(
this,
displayExecutor,
this
)
}
}
如前所述,为了解需要执行的操作,您需要检查 CameraViewModel
中是否存在 rearDisplaySession
:如果不是 null
,则表示会话已经发生,因此会关闭。另一方面,如果它是 null
,您可以使用 windowAreaController
启动一个新会话,并传递两次 Activity
。第一次被用作 Context
,第二次被用作 WindowAreaSessionCallback
监听器。
- 现在,构建并运行应用。如果您之后将设备展开并点按后方的显示按钮,系统会提示您如下消息:
- 点击立即切换屏幕,您会看到内容移到外部显示屏了!
6. 实现桌面模式
现在,您将让应用能够感知折叠:您可以根据折叠的方向将内容移到设备的一侧或设备的合页上。为此,您需要在 FoldingStateActor
内执行操作,以便将您的代码与 Activity
分离,以提高可读性。
此 API 的核心部分包含在 WindowInfoTracker
接口中,该接口是使用需要 Activity
的静态方法创建的:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
您不需要编写此代码,因为它已经存在,但了解 WindowInfoTracker
的构建方式很有帮助。
- 如需监听任何窗口更改,请在
Activity
的onResume()
方法中监听以下更改:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- 现在,打开
FoldingStateActor
文件,以填写checkFoldingState()
方法。
如您所见,它在 Activity
的 RESUMED
阶段运行,并且利用 WindowInfoTracker
来监听任何布局更改。
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
使用 WindowInfoTracker
接口,您可以调用 windowLayoutInfo()
以收集 WindowLayoutInfo
的 Flow
(其中包含 DisplayFeature
中的所有可用信息)。
最后一步是对这些变化做出反应并相应地移动内容。您需要在 updateLayoutByFoldingState()
方法中一步一步地执行此操作。
- 确保
activityLayoutInfo
包含一些DisplayFeature
属性,并且其中至少有一个属性为FoldingFeature
,否则您不需要执行任何操作:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- 计算折叠边的位置,以确保设备位置会影响您的布局,并且没有超出层次结构的边界:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
现在,您确定 FoldingFeature
会影响布局,因此您需要移动内容。
- 检查
FoldingFeature
是否为HALF_OPEN
,或者您刚刚恢复内容的位置。如果它为HALF_OPEN
,则您需要执行另一项检查,并根据折叠边的方向执行不同操作:
step1/FoldingStateActor.kt
if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
when (foldingFeature.orientation) {
FoldingFeature.Orientation.VERTICAL -> {
cameraViewfinder.moveToRightOf(foldPosition)
}
FoldingFeature.Orientation.HORIZONTAL -> {
cameraViewfinder.moveToTopOf(foldPosition)
}
}
} else {
cameraViewfinder.restore()
}
如果折叠边为 VERTICAL
,则您需要向右移动内容,否则需要将其移到折叠边顶部。
- 构建并运行您的应用,然后展开设备并将其置于桌面模式,看看内容相应地移动!
7. 恭喜
在此 Codelab 中,您了解了可折叠设备、折叠状态更改和 Rear Display API 的特别之处。