HarmonyOS 7 多设备协同:设备断点与响应式布局(连载01-03)【共创稿事节】
同一个任务中心,放在手机上是纵向列表,展开到平板又希望同时看到分类、任务和详情。真正麻烦的地方不是把宽度乘以一个系数,而是窗口随时发生变化:折叠屏由折叠态进入展开态,平板进入分屏,电脑用户拖动窗口边缘,应用得到的可用区域都与设备外壳尺寸不同。如果页面只通过设备名称决定布局,主从栏很容易挤在一起,原本能够阅读的正文被压得只剩几行。
更可靠的设计是以当前应用窗口的可用宽度驱动布局,同时把“选中了哪条任务”“筛选条件是什么”保留为独立业务状态。页面可以从单列切到两列、三列,但它们读取同一套任务数据。布局变化不应该导致网络请求重新发起,也不应把未提交的输入内容清空。本文用一个任务工作台说明从断点制定到交互复核的完整工程方法。

断点是内容决定的,不是设备名称决定的
断点通常从最拥挤的界面开始推导。任务列表需要展示名称、状态和最后更新时间,单项的理想最小宽度约为 260vp;右侧详情包含操作区域,宽度低于 320vp 时,按钮和正文就容易相互抢空间。左右两栏之间还需要留出 12~20vp 的间隔,所以将 600vp 设为单列转双列的工程起点较为合适。继续扩展到分类导航、任务列表、任务详情三个区域时,可将 840vp 作为第二个断点。这里的数值是本页面的设计选择,不属于所有应用都必须照搬的系统阈值。
本项目把宽度划为三段:不足 600vp 时优先完成单个任务,600~839vp 时侧重列表和详情,达到 840vp 时增加分类区。窗口高度、文字缩放和语言长度同样会影响内容容纳能力,因此断点只是布局规则的入口,组件还要设置合理的最小高度、留白和滚动行为。华为开发者的媒体查询资料明确支持根据应用页面的 width、height 等特征更新样式,并允许在查询条件中使用 vp 单位。
具体排布可用 GridRow 与 GridCol 实现。它们负责把栅格宽度映射到不同的断点列数,业务模型本身没有必要为每种设备复制一份。下面先展示一个适合工作台概览页的布局:小窗口一列、中等窗口两列、大窗口三列。TaskSummary 是应用内部的数据类型,列表内容可以从接口适配层注入。
interface TaskSummary {
id: string;
name: string;
progress: number;
}
@Entry
@Component
struct OverviewPage {
@State tasks: TaskSummary[] = [
{ id: 'review', name: '需求评审', progress: 40 },
{ id: 'design', name: '交互设计', progress: 70 },
{ id: 'release', name: '版本准备', progress: 25 }
];
@Builder
taskCard(item: TaskSummary) {
Column({ space: 8 }) {
Text(item.name).fontSize(20).fontWeight(FontWeight.Medium)
Text(`完成进度 ${item.progress}%`).fontSize(14)
Progress({ value: item.progress, total: 100 })
.color('#2778DC')
}
.width('100%')
.padding(16)
.backgroundColor('#F3F7FE')
.borderRadius(14)
}
build() {
GridRow({
columns: { sm: 4, md: 8, lg: 12 },
breakpoints: {
value: ['600vp', '840vp'],
reference: BreakpointsReference.WindowSize
},
gutter: { x: 12, y: 12 }
}) {
ForEach(this.tasks, (item: TaskSummary) => {
GridCol({ span: { sm: 4, md: 4, lg: 4 } }) {
this.taskCard(item)
}
}, (item: TaskSummary) => item.id)
}
.width('100%')
.padding(16)
}
}
这段代码表达的是三种宽度区间对应的栅格占比,而不是强行给屏幕固定像素值。GridCol 的 span 对应列数:小窗口占四列,中窗口占八列中的四列,大窗口占十二列中的四列。稳定的 item.id 用作列表键,数据更新时更容易维持组件身份。项目中还应避免为所有子节点都设置与父栅格相同的跨度,那会增加无效的布局层次;华为 Code Linter 也有针对这一写法的检查规则。

窗口变化时,业务状态要留在页面之外
仅靠栅格能完成概览卡片排布,却处理不了“单栏时跳到详情,大栏时并排显示”的路由语义。我们可以用媒体查询监听当前窗口宽度,将是否宽屏保存在一个页面状态里。检测依据选择 min-width 而不是 device-width:后者代表设备层面的尺寸,并不等同于当前窗口可绘制区域;例如应用进入分屏后,窗口变窄,布局应立即收缩。
import { mediaquery } from '@kit.ArkUI';
@Entry
@Component
struct TaskWorkspace {
@State wide: boolean = false;
@State selectedId: string = 'review';
private widthListener?: mediaquery.MediaQueryListener;
private onWidthChange = (result: mediaquery.MediaQueryResult): void => {
this.wide = result.matches;
};
aboutToAppear(): void {
this.widthListener = this.getUIContext()
.getMediaQuery().matchMediaSync('(min-width: 600vp)');
this.widthListener.on('change', this.onWidthChange);
// 初次布局也应依据当前窗口宽度建立状态
this.wide = this.widthListener.matches;
}
aboutToDisappear(): void {
this.widthListener?.off('change', this.onWidthChange);
}
build() {
Column() {
Text('项目任务中心').fontSize(26).fontWeight(FontWeight.Bold)
Row({ space: 12 }) {
Column({ space: 12 }) {
Button('需求评审').onClick(() => { this.selectedId = 'review'; })
Button('交互设计').onClick(() => { this.selectedId = 'design'; })
Button('版本准备').onClick(() => { this.selectedId = 'release'; })
}
.width(this.wide ? '38%' : '100%')
if (this.wide) {
Column({ space: 12 }) {
Text('当前任务').fontSize(18)
Text(this.selectedId).fontSize(24)
Text('这里接入具体任务详情与操作按钮')
}
.layoutWeight(1)
.padding(16)
.backgroundColor('#F4F8FE')
.borderRadius(12)
}
}
.width('100%')
.margin({ top: 16 })
}
.padding(20)
.width('100%')
}
}
需要留意:这段主从区代码在窄窗口保留任务列表,选择后的详情导航应由既有 Navigation 页面栈接管;宽窗口则在同页展示详情。实际工程可以把选中 ID 放进页面共享的状态对象,让窄屏路由和宽屏详情组件都订阅同一字段。这样从窄屏进入详情、扩展窗口再回到双栏时,不会因为重新创建一个详情组件就切换到默认任务。对开发者而言,真正要守住的是状态与视图分离,断点计算只负责决定哪些组件显示。
这还涉及一个常被忽略的细节:媒体查询的监听要在页面出现时绑定,页面离开时解绑。否则多次返回同一页面,监听器会累积,窗口改变一次却触发多次回调。对于展示在多个页面的全局工作台,应把监听逻辑放到与页面生命周期匹配的容器中,而不是在每个列表项里独立监听一次。
针对多窗口交互做一张真正有用的检查表
| 可用宽度变化 | 期望排布 | 核对重点 |
|---|---|---|
| 小于 600vp | 单列内容 | 按钮可点击、正文可滚动、详情有返回路径 |
| 600~839vp | 双栏或双列卡片 | 列表选择与详情同步、间距不挤压 |
| 不低于 840vp | 更充分的多列布局 | 分类可见、长标题换行、空态完整 |
| 由宽变窄 | 重新排布 | 保留选中 ID、筛选条件和未提交输入 |
| 由窄变宽 | 重新排布 | 不重复发起数据加载、滚动位置合理 |
这里的检查不应该只看页面有没有横向滚动条。字体从默认尺寸放大后是否发生遮挡,中文标题换成更长的多语言文本后是否截断,键盘弹出后详情操作区是否还能触达,都值得一并复核。图片区域应保持宽高比,含数据的卡片不要只靠颜色区分状态;在多设备之间切换输入方式时,这些细节比将每个按钮按比例拉伸更重要。

把响应式布局纳入后续上架路线
做多端适配时,一个容易拖慢研发的办法是按手机、平板、电脑分别维护三套页面。短期内每套页面都能快速达到视觉目标,但需求增加“筛选任务”“批量操作”后,同一个业务规则就得改三遍。采用共用数据模型、有限的布局分支和统一的详情路由,维护成本会稳得多。
界面是否合适应由数据可读性、操作密度和窗口行为来判断。窗口宽度是输入,状态不丢失是约束,布局只是输出。接下来的多设备适配章节会继续讨论鼠标、键盘、触控与焦点处理,使这套工作台不仅看起来适应不同尺寸,也能符合不同输入方式的交互习惯。
再强调一条工程原则:断点变化只应重新计算布局,不能悄悄改变任务列表的排序规则。假设手机采用最近更新排序,平板却换成优先级排序,用户会误以为任务在切换后消失。让筛选、排序与分页状态由业务层统一管理,不同布局只负责卡片密度和信息呈现顺序,才能避免同一工作台在不同终端出现业务语义偏差。
分栏详情较长时,还必须明确滚动容器属于哪一级:整页滚动,或左右栏各自滚动。采用嵌套滚动时需要核对手势竞争和焦点跳转,不应让用户滚动某一栏却把另一栏的内容带走。配合稳定业务键、必要的焦点恢复与清晰的空态,窗口变化才能成为自然的视觉调整,而不是一次突兀的页面重置。
相关官方资料:媒体查询与响应式设计、GridCol 栅格组件、多端栅格检查规则。
更多推荐
所有评论(0)