安卓应用开发中可折叠设备适配详解

随着华为 Mate X、三星 Galaxy Fold、小米 Mix Fold 等可折叠设备的普及,应用需要适应折叠态展开态之间的屏幕尺寸变化,以及多窗口模式(分屏、悬浮窗)下的布局调整。如果未做适配,应用可能会在折叠屏上出现布局错乱、Activity 重建导致状态丢失、UI 元素被铰链区域遮挡、多窗口下界面显示不全等问题。本文将深入分析可折叠设备适配的挑战,并提供从检测到布局响应的完整解决方案。


一、问题现象

  • 屏幕折叠/展开时布局错乱:从折叠屏展开成平板模式后,界面元素没有重新排列,出现大量空白或挤压。
  • Activity 频繁重建:默认配置变更会导致 Activity 重建,丢失用户输入或滚动位置。
  • 铰链区域遮挡:在折叠屏的屏幕中间(铰链位置),某些 UI 元素被遮挡或显示异常。
  • 多窗口模式下界面过小:应用在分屏或悬浮窗中运行时,控件密集难以操作。
  • 应用状态未保存:用户折叠屏幕后,之前浏览的位置、输入框内容丢失。
  • 摄像头区域未适配:部分设备在折叠时摄像头区域可能影响 UI。

二、产生原因

2.1 屏幕尺寸与配置变更

可折叠设备在折叠和展开时,屏幕的宽度、高度、密度等配置可能发生变化。默认情况下,这些变化会触发 Activity 的重新创建(onConfigurationChanged 默认会导致重建),从而丢失状态。

2.2 布局未使用响应式设计

布局采用固定像素或依赖绝对坐标,没有使用 Flexbox、ConstraintLayout 等弹性布局,无法适应不同的屏幕宽高比。

2.3 未处理多窗口模式

Android 7.0(API 24)开始支持多窗口模式(分屏、自由窗口),可折叠设备更常见。应用未声明 resizeableActivity 或未监听多窗口变化,导致界面无法自适应。

2.4 忽略铰链区域

部分折叠屏在屏幕中间有一条物理铰链(或显示缝),应用若将重要控件放置在中线位置,可能被遮挡。

2.5 未适配不同折叠姿态

除了完全展开和折叠,还有桌面模式(像一台小电脑)或书本模式(屏幕呈一定角度),这些姿态需要单独考虑。


三、解决方案

3.1 处理屏幕尺寸变化(避免 Activity 重建)

3.1.1 声明 configChanges 属性

AndroidManifest.xml 中为 Activity 添加 screenSize|smallestScreenSize|screenLayout|orientation,让系统在屏幕尺寸变化时不重建 Activity,而是调用 onConfigurationChanged()

<activity
    android:name=".MainActivity"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />

然后在 onConfigurationChanged() 中重新调整布局:

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    // 检查当前屏幕大小或折叠状态,重新布局
    adjustLayoutForFoldable();
}

注意:如果布局差异很大(例如从手机布局变成平板布局),仍然建议重建 Activity,但需要妥善保存状态。

3.1.2 使用 ViewModel 保存状态

无论 Activity 是否重建,ViewModel 会在配置变更时保留数据,因此将界面数据(如列表滚动位置、输入内容)存储在 ViewModel 中,可以避免丢失。

public class MyViewModel extends ViewModel {
    private MutableLiveData<Integer> scrollPosition = new MutableLiveData<>();
    // ...
}

3.2 响应式布局设计

3.2.1 使用 ConstraintLayout 和百分比宽度

利用 ConstraintLayoutlayout_constraintWidth_percent 等属性,让 UI 元素按比例分配空间。

<Button
    android:layout_width="0dp"
    app:layout_constraintWidth_percent="0.5"
    ... />
3.2.2 使用 SlidingPaneLayoutTwoPaneLayout

对于列表详情结构,使用 SlidingPaneLayout 可以自动在双屏模式下列表+详情并排,单屏下切换。

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView android:id="@+id/list_view" ... />
    <FrameLayout android:id="@+id/detail_view" ... />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>
3.2.3 提供备选布局资源

为不同的屏幕宽度(sw<N>dp)提供不同布局文件。例如:

  • res/layout/activity_main.xml(手机)
  • res/layout-sw600dp/activity_main.xml(平板或展开态)

同时,可以通过 layout 限定符分别适配折叠和展开。

3.3 监听折叠状态变化

3.3.1 使用 Jetpack WindowManager

Jetpack WindowManager 是官方提供的库,用于获取折叠屏设备的状态(折叠角度、姿势等)。

添加依赖:

implementation "androidx.window:window:1.0.0"

获取 WindowInfoTracker 并监听姿势变化:

val windowInfoTracker = WindowInfoTracker.getOrCreate(context)
val flow = windowInfoTracker.windowLayoutInfo(context)
lifecycleScope.launch(Dispatchers.Main) {
    flow.collect { layoutInfo ->
        val foldingFeature = layoutInfo.displayFeatures
            .filterIsInstance<FoldingFeature>()
            .firstOrNull()
        
        if (foldingFeature != null) {
            when (foldingFeature.state) {
                FoldingFeature.State.FLAT -> { /* 完全展开 */ }
                FoldingFeature.State.HALF_OPENED -> { /* 半折,可做特殊处理 */ }
            }
            // 获取铰链区域边界(避免放置重要控件)
            val bounds = foldingFeature.bounds
        } else {
            // 非折叠设备或完全折叠
        }
    }
}
3.3.2 正确处理铰链区域

避免将触摸或关键控件放在铰链区域(通常屏幕中央或特定位置)。通过 FoldingFeature.bounds 获取矩形,然后调整布局边距或使用 View.setPadding 留出安全区。

3.4 适配多窗口模式

3.4.1 声明支持多窗口

AndroidManifest.xml 中添加:

<activity
    android:resizeableActivity="true" />

如果不支持,则设为 false,但推荐支持。

3.4.2 监听多窗口模式变化

重写 onMultiWindowModeChanged() 方法(API 24+):

@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
    super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
    if (isInMultiWindowMode) {
        // 精简 UI,例如隐藏某些面板,调整字体大小
    } else {
        // 恢复到全屏布局
    }
}

同样,可以监听画中画模式。

3.4.3 多窗口下的布局优化
  • 使用 ConstraintLayoutLinearLayout 的权重。
  • 避免固定高度/宽度,使用 wrap_contentmatch_parent
  • 对于 RecyclerView,使用 SpanCount 动态调整列数:根据可用宽度决定一行显示几项。

3.5 测试可折叠设备

  • 使用 Android 模拟器:Android Studio 提供了可折叠设备模拟器(如 Pixel Fold、Galaxy Fold)。
  • 真实设备:尽可能在华为 Mate X、三星 Fold 等真机上测试。
  • 开发者选项:在普通平板上模拟折叠屏行为(启用“可折叠显示”选项)。

3.6 保存和恢复实例状态

当 Activity 因配置变更重建时,使用 onSaveInstanceState() 保存简单数据(如选项卡索引),与 ViewModel 配合。

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("selected_tab", tabLayout.getSelectedTabPosition());
}

四、最佳实践

  1. 优先使用 Jetpack WindowManager:官方库提供跨厂商的折叠状态检测,避免厂商私有 API。
  2. 声明 configChanges 但不要滥用:对于布局差异极大的场景,重建 Activity 更简单,但需保存状态。
  3. 使用响应式布局:ConstraintLayout、SlidingPaneLayout、FlexboxLayout 都是好选择。
  4. 为不同屏幕尺寸提供备选布局:利用 sw<N>dplayout 限定符。
  5. 动态调整列数:在代码中根据屏幕宽度计算 RecyclerView 的 spanCount。
  6. 保护铰链区域:监听 FoldingFeature,将关键控件移出铰链区域。
  7. 测试所有姿势:折叠、展开、桌面模式、半折叠(书本模式)。
  8. 多窗口测试:在分屏、自由窗口、画中画模式下验证。
  9. 提供用户设置:允许用户手动选择布局模式(例如始终使用手机风格布局),以满足不同习惯。

五、总结

可折叠设备适配的核心是响应屏幕尺寸变化正确处理多窗口模式。通过声明 configChanges、使用 ViewModel 保存状态、采用响应式布局(SlidingPaneLayout 等)、监听 WindowManager 的折叠状态以及处理铰链区域,可以构建出流畅、直观的用户体验。同时,充分利用 Android 模拟器和真实设备进行测试,确保应用在折叠态和展开态都能完美运行。随着折叠屏手机的普及,适配可折叠设备将成为提升应用竞争力的关键一环。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐