概述
媒体查询作为响应式设计的核心,在移动设备上应用十分广泛。媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。媒体查询常用于下面两种场景:
针对设备和应用的属性信息(比如显示区域
、深浅色
、分辨率
),设计出相匹配的布局
当屏幕发生动态改变时(比如分屏
、横竖屏切换
),同步更新应用的页面布局
速食版
1.定义好媒体查询的条件和保存的字段
export default class BreakpointConstants{
//屏幕大小尺寸
static readonly BREAKPOINT_SM:string = 'sm'
static readonly BREAKPOINT_MD:string = 'md'
static readonly BREAKPOINT_LG:string = 'lg'
//保存进AppStorage的字段
static readonly CURRENT_BREAKPOINT:string = 'currentBreakpoint'
//媒体查询条件
static readonly RANGE_SM:string = "(320vp <= width < 600vp)"
static readonly RANGE_MD:string = "(600vp <= width < 840vp)"
static readonly RANGE_LG:string = "(840vp <= width )"
}
2.封装好工具类,设置查询条件以及监听句柄
import mediaQuery from '@ohos.mediaquery'
import BreakpointConstants from '../constants/BreakpointConstants'
export default class BreakpointSystem{
private smListener:mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_SM)
private mdListener:mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_MD)
private lgListener:mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_LG)
smListenerCallback(result:mediaQuery.MediaQueryResult){
if (result.matches) {
AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT,BreakpointConstants.BREAKPOINT_SM)
}
}
mdListenerCallback(result:mediaQuery.MediaQueryResult){
if (result.matches) {
AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT,BreakpointConstants.BREAKPOINT_MD)
}
}
lgListenerCallback(result:mediaQuery.MediaQueryResult){
if (result.matches) {
AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT,BreakpointConstants.BREAKPOINT_LG)
}
}
register(){
this.smListener.on("change",this.smListenerCallback.bind(this))
this.mdListener.on("change",this.mdListenerCallback.bind(this))
this.lgListener.on("change",this.lgListenerCallback.bind(this))
}
unregister(){
this.smListener.off("change",this.smListenerCallback.bind(this))
this.mdListener.off("change",this.mdListenerCallback.bind(this))
this.lgListener.off("change",this.lgListenerCallback.bind(this))
}
}
3.使用封装的媒体查询工具类
import BreakpointSystem from '../common/utils/BreakpointSystem'
import BreakpointConstants from '../common/constants/BreakpointConstants'
@StorageProp("currentBreakpoint") currentBreakpoint:string = BreakpointConstants.BREAKPOINT_SM
private breakpointSystem:BreakpointSystem = new BreakpointSystem()
chooseBarPosition(){
let p = {
sm: BarPosition.End,
md: BarPosition.Start,
lg: BarPosition.Start,
}
return p[this.currentBreakpoint]
}
aboutToAppear(){
this.breakpointSystem.register()
}
aboutToDisappear(){
this.breakpointSystem.unregister()
}
build() {
//根据不同的屏幕尺寸来改编barPosition的值实现响应式布局
Tabs({ barPosition: this.chooseBarPosition() }) {
TabContent() {
RecordIndex()
}.tabBar(this.myTabar($r("app.string.tab_record"),$r("app.media.ic_calendar"),0))
TabContent() {
Text("发现")
}.tabBar(this.myTabar($r("app.string.tab_discover"),$r("app.media.discover"),1))
TabContent() {
Text("我的主页")
}.tabBar(this.myTabar($r("app.string.tab_user"),$r("app.media.ic_user_portrait"),2))
}
.width("100%")
.height("100%")
//根据不同的屏幕尺寸来改编vertical的值实现横向布局与纵向布局的切换
.vertical({
sm:false,
md:true,
lg:true,
}[this.chooseBarPosition()])
}
引入与使用流程
媒体查询通过mediaquery
模块接口,设置查询条件并绑定回调函数,在对应的条件的回调函数里更改页面布局或者实现业务逻辑,实现页面的响应式设计。具体步骤如下:
首先导入媒体查询模块
import mediaquery from '@ohos.mediaquery';
通过matchMediaSync
接口设置媒体查询条件,保存返回的条件监听句柄listener
。例如监听横屏事件
let listener = mediaquery.matchMediaSync('(orientation: landscape)');
给条件监听句柄listener绑定回调函数onPortrait
,当listener
检测设备状态变化时执行回调函数。在回调函数内,根据不同设备状态更改页面布局或者实现业务逻辑
onPortrait(mediaQueryResult) {
if (mediaQueryResult.matches) {
// do something here
} else {
// do something here
}
}
listener.on('change', onPortrait);
语法规则包括媒体类型(media-type
)、媒体逻辑操作(media-logic-operations
)和媒体特征(media-feature
)
[media-type] [media-logic-operations] [(media-feature)]
例如:
screen and (round-screen: true) :表示当设备屏幕是圆形时条件成立。
(max-height: 800) :表示当高度小于等于800vp时条件成立。
(height <= 800) :表示当高度小于等于800vp时条件成立。
screen and (device-type: tv) or (resolution < 2) :表示包含多个媒体特征的多条件复杂语句
当设备类型为tv或设备分辨率小于2时条件成立。
媒体类型(media-type
)
screen
:按屏幕相关参数进行媒体查询
媒体逻辑操作(media-logic-operations
)
媒体逻辑操作符:and、or、not、only
用于构成复杂媒体查询,也可以通过comma(, )
将其组合起来,详细解释说明如下
媒体逻辑操作符
and
将多个媒体特征(Media Feature
)以“与”的方式连接成一个媒体查询,只有当所有媒体特征都为true
,查询条件成立。另外,它还可以将媒体类型和媒体功能结合起来。例如:screen and (device-type: wearable) and (max-height: 600)
表示当设备类型是智能穿戴且应用的最大高度小于等于600
个像素单位时成立。
or
将多个媒体特征以“或”的方式连接成一个媒体查询,如果存在结果为true
的媒体特征,则查询条件成立。例如:screen and (max-height: 1000) or (round-screen: true)
表示当应用高度小于等于1000
个像素单位或者设备屏幕是圆形时,条件成立。
not
取反媒体查询结果,媒体查询结果不成立时返回true
,否则返回false
。例如:not screen and (min-height: 50) and (max-height: 600)
表示当应用高度小于50
个像素单位或者大于600
个像素单位时成立。
使用not
运算符时必须指定媒体类型。
only
当整个表达式都匹配时,才会应用选择的样式,可以应用在防止某些较早的版本的浏览器上产生歧义的场景。一些较早版本的浏览器对于同时包含了媒体类型和媒体特征的语句会产生歧义,比如:screen and (min-height: 50)
。老版本浏览器会将这句话理解成screen
,从而导致仅仅匹配到媒体类型(screen
),就应用了指定样式,使用only
可以很好地规避这种情况。
使用only
时必须指定媒体类型。
comma(, )
将多个媒体特征以“或”的方式连接成一个媒体查询,如果存在结果为true
的媒体特征,则查询条件成立。其效果等同于or
运算符。例如:screen and (min-height: 1000), (round-screen: true)
表示当应用高度大于等于1000
个像素单位或者设备屏幕是圆形时,条件成立。
媒体范围操作符包括<=,>=,<,>,详细解释说明如下表。
媒体逻辑范围操作符
<=
小于等于,例如:screen and (height <= 50)
。
>=
大于等于,例如:screen and (height >= 600)
。
<
小于,例如:screen and (height < 50)
。
>
大于,例如:screen and (height > 600)
。
媒体特征(media-feature
)
媒体特征包括应用显示区域的宽高、设备分辨率以及设备的宽高等属性,详细说明如下。
height
应用页面可绘制区域的高度。
min-height
应用页面可绘制区域的最小高度。
max-height
应用页面可绘制区域的最大高度。
width
应用页面可绘制区域的宽度。
min-width
应用页面可绘制区域的最小宽度。
max-width
应用页面可绘制区域的最大宽度。
resolution
设备的分辨率,支持dpi
,dppx
和dpcm
单位。其中:
-
dpi
表示每英寸中物理像素个数,1dpi ≈ 0.39dpcm
; -
dpcm
表示每厘米上的物理像素个数,1dpcm ≈ 2.54dpi
; -
dppx
表示每个px
中的物理像素数(此单位按96px = 1英寸
为基准,与页面中的px
单位计算方式不同),1dppx = 96dpi
。
min-resolution
设备的最小分辨率。
max-resolution
设备的最大分辨率。
orientation
屏幕的方向。
可选值:
-
orientation: portrait
(设备竖屏); -
orientation: landscape
(设备横屏)。
device-height
设备的高度。
min-device-height
设备的最小高度。
max-device-height
设备的最大高度。
device-width
设备的宽度。
device-type
设备的类型。
可选值:default、tablet
。
min-device-width
设备的最小宽度。
max-device-width
设备的最大宽度。
round-screen
屏幕类型,圆形屏幕为true
,非圆形屏幕为false
。
dark-mode
系统为深色模式时为true
,否则为false
场景示例
下例中使用媒体查询,实现屏幕横竖屏切换时,给页面文本应用添加不同的内容和样式
import mediaquery from '@ohos.mediaquery';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
let portraitFunc = null;
@Entry
@Component
struct MediaQueryExample {
@State color: string = '#DB7093';
@State text: string = 'Portrait';
// 当设备横屏时条件成立
listener = mediaquery.matchMediaSync('(orientation: landscape)');
// 当满足媒体查询条件时,触发回调
onPortrait(mediaQueryResult) {
if (mediaQueryResult.matches) { // 若设备为横屏状态,更改相应的页面布局
this.color = '#FFD700';
this.text = 'Landscape';
} else {
this.color = '#DB7093';
this.text = 'Portrait';
}
}
aboutToAppear() {
// 绑定当前应用实例
portraitFunc = this.onPortrait.bind(this);
// 绑定回调函数
this.listener.on('change', portraitFunc);
}
// 改变设备横竖屏状态函数
private changeOrientation(isLandscape: boolean) {
// 获取UIAbility实例的上下文信息
let context = getContext(this) as common.UIAbilityContext;
// 调用该接口手动改变设备横竖屏状态
window.getLastWindow(context).then((lastWindow) => {
lastWindow.setPreferredOrientation(isLandscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT)
});
}
build() {
Column({ space: 50 }) {
Text(this.text).fontSize(50).fontColor(this.color)
Text('Landscape').fontSize(50).fontColor(this.color).backgroundColor(Color.Orange)
.onClick(() => {
this.changeOrientation(true);
})
Text('Portrait').fontSize(50).fontColor(this.color).backgroundColor(Color.Orange)
.onClick(() => {
this.changeOrientation(false);
})
}
.width('100%').height('100%')
}
}