问题描述
应用需要适配不同尺寸的设备(手机、平板、折叠屏),如何实现响应式布局?如何使用 layoutWeight 和 Grid 实现弹性布局?
关键字: 响应式布局、屏幕适配、layoutWeight、Grid 布局、多设备适配
解决方案
完整代码
/**
* 响应式统计卡片
*/
@Component
struct ResponsiveStatsCard {
@State stats: Array<{title: string, value: string, color: string}> = [
{ title: '总收入', value: '¥5000', color: '#ff6b6b' },
{ title: '总支出', value: '¥3000', color: '#67c23a' },
{ title: '净收益', value: '¥2000', color: '#409eff' },
{ title: '记录数', value: '50笔', color: '#e6a23c' }
];
build() {
// 使用Grid实现响应式布局
Grid() {
ForEach(this.stats, (stat: any) => {
GridItem() {
Column({ space: 8 }) {
Text(stat.value)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(stat.color)
Text(stat.title)
.fontSize(14)
.fontColor('#999')
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
}
})
}
.columnsTemplate('1fr 1fr') // 2列布局
.rowsGap(12)
.columnsGap(12)
.width('100%')
.padding(16)
}
}
/**
* 弹性权重布局
*/
@Component
struct FlexWeightLayout {
build() {
Row() {
// 左侧固定宽度
Column() {
Text('固定区域')
.fontSize(16)
}
.width(100)
.height('100%')
.backgroundColor('#e3f2fd')
// 中间弹性区域
Column() {
Text('弹性区域')
.fontSize(16)
}
.layoutWeight(1) // 占据剩余空间
.height('100%')
.backgroundColor('#fff3e0')
// 右侧固定宽度
Column() {
Text('固定区域')
.fontSize(16)
}
.width(100)
.height('100%')
.backgroundColor('#f3e5f5')
}
.width('100%')
.height(200)
}
}
/**
* 媒体查询适配
*/
import { mediaQuery } from '@kit.ArkUI';
@Entry
@Component
struct AdaptiveLayout {
@State isTablet: boolean = false;
private listener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync('(min-width: 600vp)');
aboutToAppear() {
this.listener.on('change', (result: mediaQuery.MediaQueryResult) => {
this.isTablet = result.matches;
});
this.isTablet = this.listener.matches;
}
aboutToDisappear() {
this.listener.off('change');
}
build() {
Column() {
if (this.isTablet) {
// 平板布局:左右分栏
Row() {
// 左侧列表
List() {
ForEach([1,2,3,4,5], (item: number) => {
ListItem() {
Text(`项目 ${item}`)
.width('100%')
.padding(16)
}
})
}
.width('40%')
.backgroundColor('#f5f5f5')
// 右侧详情
Column() {
Text('详情内容')
.fontSize(20)
}
.layoutWeight(1)
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
} else {
// 手机布局:单列
List() {
ForEach([1,2,3,4,5], (item: number) => {
ListItem() {
Text(`项目 ${item}`)
.width('100%')
.padding(16)
}
})
}
.width('100%')
.height('100%')
}
}
}
}
/**
* GridRow响应式布局
*/
@Component
struct GridRowDemo {
build() {
GridRow({
columns: { sm: 4, md: 8, lg: 12 }, // 不同断点的列数
gutter: { x: 12, y: 12 }, // 间距
breakpoints: { value: ['320vp', '600vp', '840vp'] } // 断点
}) {
// 小屏占4列,中屏占4列,大屏占3列
GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
Column() {
Text('卡片1')
}
.width('100%')
.height(100)
.backgroundColor('#e3f2fd')
.borderRadius(8)
}
GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
Column() {
Text('卡片2')
}
.width('100%')
.height(100)
.backgroundColor('#fff3e0')
.borderRadius(8)
}
GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
Column() {
Text('卡片3')
}
.width('100%')
.height(100)
.backgroundColor('#f3e5f5')
.borderRadius(8)
}
GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
Column() {
Text('卡片4')
}
.width('100%')
.height(100)
.backgroundColor('#e0f7fa')
.borderRadius(8)
}
}
.width('100%')
.padding(16)
}
}
原理解析
1. layoutWeight 弹性布局
.layoutWeight(1) // 占据剩余空间
- 多个组件设置 layoutWeight 会按比例分配
- 常用于左右分栏、上下分栏
2. Grid 网格布局
.columnsTemplate('1fr 1fr') // 2列等宽
.columnsTemplate('100px 1fr 2fr') // 固定+弹性
- fr 单位表示剩余空间的份数
- 支持固定宽度和弹性宽度混合
3. 媒体查询
mediaQuery.matchMediaSync('(min-width: 600vp)')
- 监听屏幕宽度变化
- 根据断点切换布局
4. GridRow 响应式
columns: { sm: 4, md: 8, lg: 12 }
- 自动根据屏幕宽度选择列数
- span 设置占据的列数
最佳实践
- 优先使用 layoutWeight: 简单场景用 layoutWeight
- Grid 适合卡片: 统计卡片、功能入口用 Grid
- 媒体查询: 大幅布局变化用媒体查询
- GridRow: 复杂响应式用 GridRow
- 断点设置: 320vp(手机)、600vp(平板)、840vp(PC)
避坑指南
- layoutWeight 父容器: 父容器必须有明确宽度/高度
- Grid 高度: Grid 需要设置高度或使用 layoutWeight
- 媒体查询内存: 组件销毁时 off 监听
- GridRow 嵌套: 避免 GridRow 嵌套 GridRow
- 性能: 媒体查询变化时避免大量重绘
效果展示
- 手机(320-600vp):单列布局,卡片占满宽
- 平板(600-840vp):双列布局,左右分栏
- PC(>840vp):多列布局,最大化利用空间
相关资源