大型展开式显示屏和独特的折叠状态为可折叠设备带来了全新的用户体验。要使您的应用支持折叠,请使用 Jetpack WindowManager 库,该库为可折叠设备窗口功能(例如折叠和铰链)提供了一个 API 表面。当您的应用支持折叠时,它可以调整其布局以避免将重要内容放置在折叠或铰链区域,并使用折叠和铰链作为自然分隔线。
窗口信息
Jetpack WindowManager 中的 WindowInfoTracker
接口公开了窗口布局信息。该接口的 windowLayoutInfo()
方法返回 WindowLayoutInfo
数据流,通知您的应用有关可折叠设备折叠状态的信息。WindowInfoTracker
的 getOrCreate()
方法创建 WindowInfoTracker
的实例。
WindowManager 提供了对使用 Kotlin Flows 和 Java 回调收集 WindowLayoutInfo
数据的支持。
Kotlin Flows
要启动和停止 WindowLayoutInfo
数据收集,可以使用 可重启的生命周期感知协程,其中 repeatOnLifecycle
代码块在生命周期至少为 STARTED
时执行,并在生命周期为 STOPPED
时停止。当生命周期再次变为 STARTED
时,代码块的执行会自动重启。在以下示例中,代码块收集和使用 WindowLayoutInfo
数据
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
.windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}
Java 回调
androidx.window:window-java
依赖项中包含的回调兼容层使您能够收集 WindowLayoutInfo
更新,而无需使用 Kotlin Flow。该工件包含 WindowInfoTrackerCallbackAdapter
类,该类将 WindowInfoTracker
调整为支持注册(和注销)回调以接收 WindowLayoutInfo
更新,例如
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
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());
windowInfoTracker =
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoTracker.addWindowLayoutInfoListener(
this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoTracker
.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
public void accept(WindowLayoutInfo newLayoutInfo) {
SplitLayoutActivity.this.runOnUiThread( () -> {
// Use newLayoutInfo to update the layout.
});
}
}
}
RxJava 支持
如果您已经使用 RxJava
(版本 2
或 3
),您可以利用使您能够使用 Observable
或 Flowable
收集 WindowLayoutInfo
更新(而无需使用 Kotlin Flow)的工件。
由 androidx.window:window-rxjava2
和 androidx.window:window-rxjava3
依赖项提供的兼容性层包含 WindowInfoTracker#windowLayoutInfoFlowable()
和 WindowInfoTracker#windowLayoutInfoObservable()
方法,这些方法允许您的应用程序接收 WindowLayoutInfo
更新,例如
class RxActivity: AppCompatActivity {
private lateinit var binding: ActivityRxBinding
private var disposable: Disposable? = null
private lateinit var observable: Observable<WindowLayoutInfo>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Create a new observable
observable = WindowInfoTracker.getOrCreate(this@RxActivity)
.windowLayoutInfoObservable(this@RxActivity)
}
@Override
protected void onStart() {
super.onStart();
// Subscribe to receive WindowLayoutInfo updates
disposable?.dispose()
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { newLayoutInfo ->
// Use newLayoutInfo to update the layout
}
}
@Override
protected void onStop() {
super.onStop();
// Dispose the WindowLayoutInfo observable
disposable?.dispose()
}
}
可折叠显示器的功能
Jetpack WindowManager 的 WindowLayoutInfo
类将显示窗口的功能作为 DisplayFeature
元素列表提供。
一个 FoldingFeature
是一种 DisplayFeature
类型,提供有关可折叠显示器的信息,包括以下内容
state
: 设备的折叠状态,FLAT
或HALF_OPENED
orientation
: 折叠或铰链的方向,HORIZONTAL
或VERTICAL
occlusionType
: 折叠或铰链是否遮挡部分显示,NONE
或FULL
isSeparating
: 折叠或铰链是否创建两个逻辑显示区域,true 或 false
处于 HALF_OPENED
状态的可折叠设备始终报告 isSeparating
为 true,因为屏幕被分成两个显示区域。此外,当应用程序跨越两个屏幕时,isSeparating
在双屏设备上始终为 true。
FoldingFeature
的 bounds
属性(继承自 DisplayFeature
)表示折叠特征(如折叠或铰链)的边界矩形。边界可用于相对于特征在屏幕上定位元素。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from windowInfoRepo when the lifecycle is STARTED // and stops collection when the lifecycle is STOPPED WindowInfoTracker.getOrCreate(this@MainActivity) .windowLayoutInfo(this@MainActivity) .collect { layoutInfo -> // New posture information val foldingFeature = layoutInfo.displayFeatures .filterIsInstance() .firstOrNull() // Use information from the foldingFeature object } } } }
Java
private WindowInfoTrackerCallbackAdapter windowInfoTracker; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... windowInfoTracker = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this)); } @Override protected void onStart() { super.onStart(); windowInfoTracker.addWindowLayoutInfoListener( this, Runnable::run, layoutStateChangeCallback); } @Override protected void onStop() { super.onStop(); windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Use newLayoutInfo to update the Layout List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures(); for (DisplayFeature feature : displayFeatures) { if (feature instanceof FoldingFeature) { // Use information from the feature object } } } }
桌面模式
利用 FoldingFeature
对象中包含的信息,您的应用程序可以支持诸如桌面模式之类的姿势,其中手机放在表面上,铰链处于水平位置,可折叠屏幕半开。
桌面模式为用户提供了无需手持手机即可操作手机的便利。桌面模式非常适合观看媒体、拍照和进行视频通话。
使用 FoldingFeature.State
和 FoldingFeature.Orientation
来确定设备是否处于桌面模式
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Java
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
一旦您知道设备处于桌面模式,请相应地更新您的应用程序布局。对于媒体应用程序,这通常意味着将播放放在折叠上方,并将控件和辅助内容放置在下方,以实现免提观看或聆听体验。
示例
MediaPlayerActivity
应用程序:了解如何使用 Media3 Exoplayer 和 WindowManager 创建一个折叠感知视频播放器。展开您的相机体验 代码实验室:了解如何为摄影应用程序实施桌面模式。将取景器显示在屏幕上半部分(折叠上方),将控件显示在下半部分(折叠下方)。
书本模式
另一种独特的可折叠姿势是书本模式,其中设备半开,铰链处于垂直位置。书本模式非常适合阅读电子书。在像装订的书本一样打开的大屏幕可折叠设备上采用两页布局,书本模式可以捕捉到阅读真本书的体验。
如果您想在免提拍照时捕捉不同的纵横比,也可以使用它。
使用与桌面模式相同的技术实施书本模式。唯一的区别是代码应该检查折叠特征方向是垂直的还是水平的。
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
Java
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
窗口大小变化
应用程序的显示区域可能会因设备配置更改而发生变化,例如,当设备折叠或展开、旋转或在多窗口模式下调整窗口大小时。
Jetpack WindowManager 的 WindowMetricsCalculator
类允许您检索当前和最大窗口指标。与 API 级别 30 中引入的平台 WindowMetrics
一样,WindowManager WindowMetrics
提供窗口边界,但 API 向后兼容到 API 级别 14。
参见 窗口大小类。
其他资源
示例
- Jetpack WindowManager:如何使用 Jetpack WindowManager 库的示例
- Jetcaster:使用 Compose 实现桌面姿势
代码实验室
为您推荐
- 注意:当 JavaScript 关闭时,将显示链接文本。
- 使用 Jetpack WindowManager 支持可折叠和双屏设备
- 使用 Jetpack WindowManager 优化您的相机应用程序在可折叠设备上的运行
- 设备兼容性模式