Appearance
折叠屏/平板适配
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/Rowarkts
// 相对布局适配折叠屏
@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 优势。强调鸿蒙在多端适配上的独特优势。