响应式布局
自适应布局可以保证窗口尺寸在【一定范围内变化】时,页面的显示是正常的。但是将窗口尺寸【变化较大】时(如窗口宽度从400vp变化为1000vp),仅仅依靠自适应布局可能出现图片异常放大或页面内容稀疏、留白过多等问题,此时就需要借助响应式布局能力调整页面结构。
响应式布局是指页面内的元素可以根据特定的特征(如窗口宽度、屏幕方向等)自动变化以适应外部容器变化的布局能力。
响应式布局中最常使用的特征是窗口宽度,可以将窗口宽度划分为不同的范围(下文中称为断点)。当窗口宽度从一个断点变化到另一个断点时,改变页面布局(如将页面内容从单列排布调整为双列排布甚至三列排布等)以获得更好的显示效果。
三种响应式布局能力:
响应式布局能力 | 简介 |
---|---|
断点 | 将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。 |
媒体查询 | 媒体查询支持监听窗口宽度、横竖屏、深浅色、设备类型等多种媒体特征,当媒体特征发生改变时同步调整页面布局。 |
栅格布局 | 栅格组件将其所在的区域划分为有规律的多列,通过调整不同断点下的栅格组件的参数以及其子组件占据的列数等,实现不同的布局效果。 |
断点
断点以应用窗口宽度为切入点,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,在不同的区间下,开发者可根据需要实现不同的页面布局效果。
断点名称 | 取值范围(vp) | 设备 |
---|---|---|
xs | [0, 320) | 手表等超小屏 |
sm | [320, 600) | 手机竖屏 |
md | [600, 840) | 手机横屏,折叠屏 |
lg | [840, +∞) | 平板,2in1 设备 |
系统提供了多种方法,判断应用当前处于何种断点,进而可以调整应用的布局。常见的监听断点变化的方法如下所示:
- 获取窗口对象并监听窗口尺寸变化
- 通过媒体查询监听应用窗口尺寸变化
- 借助栅格组件能力监听不同断点的变化
通过窗口对象,监听窗口尺寸变化
在 EntryAbility 中添加监听
import window from '@ohos.window'
import display from '@ohos.display'
import UIAbility from '@ohos.app.ability.UIAbility'
export default class EntryAbility extends UIAbility {
private windowObj?: window.Window
private curBp: string = ''
//...
// 根据当前窗口尺寸更新断点
private updateBreakpoint(windowWidth: number) :void{
// 将长度的单位由px换算为vp
let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels
let newBp: string = ''
if (windowWidthVp < 320) {
newBp = 'xs' // 超小屏
} else if (windowWidthVp < 600) {
newBp = 'sm' // 小屏
} else if (windowWidthVp < 840) {
newBp = 'md' // 中屏
} else {
newBp = 'lg' // 大屏
}
if (this.curBp !== newBp) {
this.curBp = newBp
// 使用状态变量记录当前断点值
AppStorage.setOrCreate('currentBreakpoint', this.curBp)
}
}
onWindowStageCreate(windowStage: window.WindowStage) :void{
windowStage.getMainWindow().then((windowObj) => {
this.windowObj = windowObj
// 获取应用启动时的窗口尺寸
this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width)
// 注册回调函数,监听窗口尺寸变化
windowObj.on('windowSizeChange', (windowSize)=>{
this.updateBreakpoint(windowSize.width)
})
});
// ...
}
//...
}
媒体查询
媒体查询常用于下面两种场景:
- 针对设备和应用的属性信息(比如显示区域、深浅色、分辨率),设计出相匹配的布局。
- 当屏幕发生动态改变时(比如分屏、横竖屏切换),同步更新应用的页面布局。
断点工具封装
/*
定义一个接口类型
键: 断点值
值: 泛型
*/
declare interface BreakPointTypeOption<T> {
xs?: T
sm?: T
md?: T
lg?: T
xl?: T
xxl?: T
}
/*
对外导出一个类
在实例化的时候接收一个泛型
*/
export class BreakPointType<T> {
// 选项对象
options: BreakPointTypeOption<T>
constructor(option: BreakPointTypeOption<T>) {
this.options = option
}
getValue(currentBreakPoint: string) {
if (currentBreakPoint === 'xs') {
return this.options.xs
} else if (currentBreakPoint === 'sm') {
return this.options.sm
} else if (currentBreakPoint === 'md') {
return this.options.md
} else if (currentBreakPoint === 'lg') {
return this.options.lg
} else if (currentBreakPoint === 'xl') {
return this.options.xl
} else if (currentBreakPoint === 'xxl') {
return this.options.xxl
} else {
return undefined
}
}
}
示例代码:根据不同断点改变颜色
// 1. 导入BreakPointType
import { BreakPointType } from 'xxx'
@entry
@Component
struct ComB {
// 2. 通过 AppStorage 获取断点值
@StorageProp('currentBreakpoint') currentBreakpoint: CurrentBreakpoint = 'xs'
build() {
Column() {
Text(this.currentBreakpoint)
}
.width(200)
.height(200)
.backgroundColor(
// 3. 实例化 设置不同断点的取值,并通过 getValue 根据当前断点值对应的值
new BreakPointType({
xs: Color.Red,
sm: Color.Yellow,
md: Color.Blue,
lg: Color.Green
}).getValue(this.currentBreakpoint)
)
}
}
栅格布局 Grid
栅格组件的本质是:将组件划分为有规律的多列,通过调整【不同断点】下的【栅格组件的列数】,及【子组件所占列数】实现不同布局
比如:
参考栅格列数设置:
核心用法
GridRow(){} 和 Gridclo(){}自带断点属性
// 行
GridRow(属性){
// 列
GridCol(属性){
}
}
示例代码:根据不同断点设置列数
@Entry
@Component
struct Demo12 {
@State currentBreakPoint: string = ''
build() {
Column() {
// GridRow 默认支持 4 个断点
// xs:(0vp<=width<320vp) 智能穿戴,比如手表
// sm:(320vp<=width<600vp) 手机
// md:(600vp<=width<840vp) 折叠屏
// lg:(840vp<=width) 平板
GridRow({
breakpoints: {
value: ['320vp', '600vp', '840vp']
},
gutter: 10, // 子项之间的间距
// columns: 12, // 设置一行的总列数, 默认: 一行12列
// 可以根据断点值, 设置每一行的列数
columns: {
xs: 2, // 超小屏, 比如: 手表
sm: 4, // 小屏幕, 比如: 手机竖屏
md: 8, // 中等屏幕, 比如: 折叠屏, 手机横屏
lg: 12, // 大屏幕, 比如: pad
}
}) {
ForEach(Array.from({ length: 2 }), (item: string, index: number) => {
GridCol({
// 设置一列占得份数
// span: 2,
// 支持不同断点分别设置不同的占用列数
span: {
xs: 1,
sm: 1,
md: 1,
lg: 1
},
// offset 偏移列数 默认为 0
// offset: 1, // 偏移一列
// 支持不同断点分别设置偏移不同的列数
offset: {
sm: 1
}
}) {
Text(index.toString())
}
.height(100)
.border({ width: 1, color: Color.Black })
})
}
.width('90%')
.height('90%')
.border({ width: 1, color: Color.Orange })
// 断点发生变化时触发回调
.onBreakpointChange((breakPoint) => {
console.log('breakPoint', breakPoint)
this.currentBreakPoint = breakPoint
})
Text(`断点值: ${this.currentBreakPoint}`)
.fontSize(30)
}
.width('100%')
.height('100%')
.backgroundColor('#dcdfe8')
}
}