Skip to content

折叠屏/平板适配

1. 折叠屏适配概述

1.1 折叠屏形态

鸿蒙折叠屏形态:
┌─────────────────────────────────────────────────────┐
│  折叠屏形态分类                                     │
│  ├── 翻盖式(Flip):外屏 + 内屏(如 Mate X Flip)   │
│  ├── 折屏式(Fold):展开/折叠(如 Mate X5)        │
│  ├── 多折屏:多次折叠(如 Mate XTs)               │
│  └── 卷轴屏:屏幕展开/收缩                          │
├─────────────────────────────────────────────────────┤
│  适配挑战:                                         │
│  ├── 多屏幕尺寸:7.8" / 8.0" / 10.2" / 12.6"      │
│  ├── 折痕区域:屏幕中间有物理折痕                    │
│  ├── 多窗口:同时显示多个应用                        │
│  ├── 折叠状态:折叠/展开/半折叠三种状态              │
│  └── 交互方式:多角度悬停、手势操作                  │
└─────────────────────────────────────────────────────┘

1.2 鸿蒙折叠屏适配策略

适配策略:
┌───────────────────────┬───────────────────────┐
│ 策略                  │ 适用场景              │
├───────────────────────┼───────────────────────┤
│ 断点适配              │ 不同尺寸显示不同布局   │
│ 栅格布局              │ 网格化自动排列        │
│ 相对布局              │ RelativeContainer 灵活布局 │
│ 多窗口                │ 分屏/浮动窗口         │
│ 状态感知              │ 根据折叠状态切换 UI    │
│ 媒体查询              │ 响应式 CSS 条件       │
└───────────────────────┴───────────────────────┘

2. 断点与多端适配

2.1 断点机制

arkts
// 使用媒体查询进行断点适配
@Entry
@Component
struct FoldablePage {
  @State isFolded: boolean = false;
  @State isTablet: boolean = false;

  // 断点定义
  private breakpoints: Breakpoints = {
    small: 0,
    medium: 600,
    large: 1024,
    xlarge: 1440
  };

  build() {
    // 使用媒体查询
    Column() {
      this.renderContent()
    }
    .onVisibleAreaChange((visibleArea: number) => {
      this.isTablet = visibleArea > 0.9;
    })
  }

  @Builder
  renderContent() {
    // 根据屏幕尺寸渲染不同布局
    if (this.isFolded) {
      // 折叠状态:单列布局
      SingleColumnLayout()
    } else if (this.isTablet) {
      // 展开状态/平板:双列布局
      DoubleColumnLayout()
    } else {
      // 手机:默认布局
      DefaultLayout()
    }
  }
}

2.2 媒体查询适配

arkts
// 媒体查询:响应式布局
@Entry
@Component
struct ResponsivePage {
  build() {
    Column() {
      // 媒体查询容器
      this.renderContent()
    }
  }

  @Builder
  renderContent() {
    Row() {
      // 侧边栏(仅在大屏显示)
      this.renderSidebar()
        .width('25%')

      // 主内容
      this.renderMainContent()
        .width('75%')
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  renderSidebar() {
    Column() {
      Text('侧边栏')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
    }
    .backgroundColor('#f0f0f0')
  }

  @Builder
  renderMainContent() {
    Column() {
      Text('主内容区')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
    }
    .backgroundColor('#ffffff')
  }
}

3. 折叠状态感知

3.1 折叠状态监听

arkts
import { window } from '@kit.WindowKit';

// 折叠状态监听
class FoldManager {
  private currentFoldAngle: number = 0;
  private foldState: FoldState = FoldState.UNKNOWN;

  async init() {
    // 监听折叠角度变化
    window.getMainWindow().then((win: window.Window) => {
      win.on('foldAngleChange', (angle: number) => {
        this.currentFoldAngle = angle;
        this.updateFoldState(angle);
      });
    });

    // 监听窗口状态变化
    window.on('windowStateChange', (state: window.WindowState) => {
      if (state === window.WindowState.MULTI_WINDOW) {
        // 多窗口模式
      }
    });
  }

  private updateFoldState(angle: number) {
    if (angle < 45) {
      this.foldState = FoldState.FOLDED;  // 折叠
    } else if (angle > 135) {
      this.foldState = FoldState.UNFOLDED;  // 展开
    } else {
      this.foldState = FoldState.HALF_FOLDED;  // 半折叠
    }
  }
}

enum FoldState {
  FOLDED,
  HALF_FOLDED,
  UNFOLDED,
  UNKNOWN
}

3.2 根据折叠状态切换 UI

arkts
@Entry
@Component
struct FoldAwarePage {
  @State foldState: FoldState = FoldState.UNKNOWN;

  build() {
    Column() {
      this.renderByFoldState()
    }
    .onAppear(() => {
      // 初始化折叠状态
      this.initFoldState();
    })
  }

  @Builder
  renderByFoldState() {
    switch (this.foldState) {
      case FoldState.FOLDED:
        // 折叠:紧凑布局
        Column() {
          CompactCard()
          CompactList()
        }
        .width('100%')
        break;

      case FoldState.HALF_FOLDED:
        // 半折叠:分栏布局
        Row() {
          HalfLeft()
          HalfRight()
        }
        .width('100%')
        break;

      case FoldState.UNFOLDED:
        // 展开:宽屏布局
        Row() {
          Sidebar()
          MainContent()
        }
        .width('100%')
        break;

      default:
        DefaultLayout()
    }
  }

  async initFoldState() {
    const win = await window.getMainWindow();
    // 获取当前折叠状态
  }
}

4. 多窗口适配

4.1 多窗口模式

arkts
// 鸿蒙支持多种窗口模式
enum WindowMode {
  FULL_SCREEN,       // 全屏模式
  MULTI_WINDOW,      // 多窗口模式
  FLOATING_WINDOW,   // 浮动窗口
  PIP_WINDOW,        // 画中画
  SPLIT_SCREEN       // 分屏
}

// 窗口管理
import { window } from '@kit.WindowKit';

// 1. 检查多窗口支持
const isMultiWindowSupported = window.isMultiWindowSupported();

// 2. 获取窗口管理器
const windowManager = window.getWindowManager();

// 3. 设置窗口模式
await windowManager.setWindowMode({
  mode: WindowMode.SPLIT_SCREEN,
  splitRatio: 0.5  // 50% 分屏
});

// 4. 窗口状态监听
window.on('windowSizeChange', (size: window.WindowSize) => {
  console.log(`Window size: ${size.width} x ${size.height}`);
  this.onResize(size);
});

4.2 分屏适配

arkts
// 分屏适配:根据窗口大小动态调整
@Entry
@Component
struct SplitScreenPage {
  @State contentWidth: number = 0;
  @State contentHeight: number = 0;

  onWindowStageCreate(windowStage: app.WindowStage) {
    windowStage.getMainWindow().then((win: window.Window) => {
      win.on('windowSizeChange', (size: window.WindowSize) => {
        this.contentWidth = size.width;
        this.contentHeight = size.height;
      });
    });
  }

  build() {
    // 自适应宽度组件
    Row() {
      ForEach(this.generateItems(), (item: Item) => {
        ItemCard()
          .width(this.contentWidth / 3 - 10)  // 自适应宽度
          .aspectRatio(1)
      }, (item: Item) => item.id)
    }
    .width('100%')
    .justifyContent(FlexAlign.CENTER)
    .alignItems(VerticalAlign.CENTER)
  }
}

5. 平板适配

5.1 平板 UI 设计原则

平板 UI 设计原则:
├── 充分利用大屏空间(不要简单拉伸手机布局)
├── 使用多列布局(2-4 列)
├── 导航结构扁平化(底部导航/侧边栏)
├── 字体/图标适当放大(至少 1.5x)
├── 减少手势操作(大屏适合按钮/卡片)
├── 支持分屏/浮动窗口
└── 适配横屏/竖屏

5.2 平板布局模板

arkts
// 平板宽屏布局
@Entry
@Component
struct TabletPage {
  build() {
    Row() {
      // 左侧导航栏
      NavigationBar()
        .width(280)
        .height('100%')

      // 中间内容区
      Column() {
        TopBar()
        MainContent()
      }
      .width('60%')
      .height('100%')

      // 右侧信息面板(仅在大屏显示)
      InfoPanel()
        .width(320)
        .height('100%')
    }
    .width('100%')
    .height('100%')
  }
}

// 导航栏
@Component
struct NavigationBar {
  build() {
    Column() {
      Text('导航')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 })

      ListItem({ name: '首页', icon: '🏠' })
      ListItem({ name: '消息', icon: '📬' })
      ListItem({ name: '设置', icon: '⚙️' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

// 信息面板(仅在大屏显示)
@Component
struct InfoPanel {
  build() {
    Column() {
      Text('详细信息')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      Divider()
      // 详情内容
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#fafafa')
  }
}

6. 相对布局适配

6.1 RelativeContainer 优势

RelativeContainer 在折叠屏/平板中的优势:
├── 不依赖固定尺寸,自动适配
├── 组件相对定位,响应窗口大小变化
├── 支持断点/媒体查询
├── 适合复杂布局(如平板的多面板)
└── 性能优于嵌套的 Column/Row
arkts
// 相对布局适配折叠屏
@Entry
@Component
struct RelativeFoldablePage {
  build() {
    RelativeContainer() {
      // 1. 定义区域(Regions)
      this.defineRegions();

      // 2. 内容区域
      Column() {
        Text('内容区')
          .fontSize(24)
      }
      .id('content')
      .alignRules({
        top: { anchor: 'toolbar', align: VerticalAlign.Top },
        bottom: { anchor: 'status_bar', align: VerticalAlign.Bottom },
        left: { anchor: 'sidebar', align: HorizontalAlign.Left },
        right: { anchor: 'info_panel', align: HorizontalAlign.Right }
      })

      // 3. 侧边栏
      Column() {
        Text('侧边栏')
      }
      .id('sidebar')
      .alignRules({
        top: { anchor: 'toolbar', align: VerticalAlign.Top },
        bottom: { anchor: 'content', align: VerticalAlign.Bottom },
        left: { anchor: ParentLayout.Left }
      })

      // 4. 工具栏
      Row() {
        Text('工具栏')
      }
      .id('toolbar')
      .alignRules({
        top: { anchor: ParentLayout.Top },
        left: { anchor: ParentLayout.Left },
        right: { anchor: ParentLayout.Right }
      })

      // 5. 信息面板(仅在大屏显示)
      Column() {
        Text('信息面板')
      }
      .id('info_panel')
      .alignRules({
        top: { anchor: 'toolbar', align: VerticalAlign.Top },
        bottom: { anchor: 'content', align: VerticalAlign.Bottom },
        right: { anchor: ParentLayout.Right }
      })
    }
    .width('100%')
    .height('100%')
  }

  defineRegions() {
    const regionWidth = Math.min(300, this.getDisplayWidth() * 0.2);
    const panelWidth = Math.min(320, this.getDisplayWidth() * 0.2);

    this.addArea('sidebar', Area.LEFT, { width: regionWidth });
    this.addArea('info_panel', Area.RIGHT, { width: panelWidth });
  }

  getDisplayWidth(): number {
    return 1080; // 实际通过 window API 获取
  }
}

7. 🎯 面试高频考点

Q1: 折叠屏适配的策略有哪些?

答要点

  • 断点适配:不同屏幕宽度使用不同布局
  • 媒体查询:响应式条件布局
  • 折叠状态感知:监听折叠角度切换 UI
  • 相对布局:RelativeContainer 自适应
  • 栅格布局:网格化自动排列
  • 多窗口支持:分屏/浮动窗口
  • 核心原则:根据状态/尺寸动态调整,而非固定布局

Q2: 平板 UI 设计有哪些最佳实践?

答要点

  • 充分利用大屏空间(多列布局)
  • 导航扁平化(底部导航/侧边栏)
  • 字体/图标放大(至少 1.5x)
  • 支持分屏/浮动窗口
  • 适配横屏/竖屏
  • 减少手势操作(多用按钮/卡片)
  • 不要简单拉伸手机布局

Q3: RelativeContainer 相比嵌套布局的优势?

答要点

  • 自适应:不依赖固定尺寸,自动适配窗口变化
  • 组件相对定位:基于锚点和规则定位
  • 性能更好:比多层嵌套 Column/Row 性能优
  • 适合复杂布局:平板多面板、折叠屏多状态
  • 断点支持:可配合媒体查询

💡 面试提示:折叠屏/平板适配是鸿蒙面试的亮点题。重点掌握 折叠状态感知断点适配多窗口适配RelativeContainer 优势。强调鸿蒙在多端适配上的独特优势。