一 多端适配
1.1 页面适配 (自适应布局)
1.1.1 拉伸 > Flex、Column、Row 支持属性
- 屏幕宽 - image宽 - 俩row宽 > 0(有剩余), 则image拉伸占满剩余空间, 俩row原来bility
- 屏幕宽 - image宽 - 俩row宽 <= 0(没剩余), 则image保持原比例, 俩row缩小
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Row()
.width(150)
.height(400)
.backgroundColor(Color.Yellow)
.flexGrow(0) // 拉伸: 当父容器有剩余空间时,保持原始尺寸 ( 设置0 ); 按比例拉伸自身 ( 设置1 )
.flexShrink(1) // 缩小: 当父容器空间不够时,不压缩,保持原始尺寸 ( 设置0 ); 参与压缩,按比例缩小自身 ( 设置1 )
Image($r('app.media.009'))
.width(300)
.height(400)
.objectFit(ImageFit.Contain)
.backgroundColor(Color.Red)
.flexGrow(1) //
.flexShrink(0) //
Row()
.width(150)
.height(400)
.backgroundColor(Color.Blue)
.flexGrow(0) //
.flexShrink(1) //
}
.width(this.sliderWidth)
.backgroundColor(Color.White)
.padding(10)
1.1.2 拉伸> Blank()
- 跟随屏幕宽度 伸缩
Row() {
Text('飞行模式')
.fontSize(16)
.width(135)
.height(22)
.fontWeight(FontWeight.Medium)
.lineHeight(22)
// Implement stretch capability using the Blank component.
Blank()
Toggle({ type: ToggleType.Switch })
.width(36)
.height(20)
}
- FlexAlign.SpaceEvenly 始终保持组件间隔等宽
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.list, () => {
this.Item();
}, (item: number, index: number) => item.toString() + index)
}
1.1.3 缩放 > layoutWeight属性
- 无论屏幕对款 三个text都是等宽
Row() {
Text()
.layoutWeight(1)
Text()
.layoutWeight(1)
Text()
.layoutWeight(1)
}
1.1.4 缩放 > aspectRatio
- 屏幕变小 会导致组件等比例缩小
Column() {
Image($r('app.media.startIcon'))
}
.aspectRatio(1) //保持宽高比为1 正方形
.border({ width: 2, color: '#66F1CCB8' })
}
1.1.5 延伸 > List Scroll
- list scroll宽度变化 导致内部contentSize变化
Row() {
Row({ space: 10 }) {
List({ space: 10 }) {
ForEach(this.appList, () => {
ListItem() {
Column() {
CustomBuild() // 自定义组件
}
.width(80)
.height(102)
}
.width(80)
.height(102)
}, (item: number, index: number) => item.toString() + index)
}
.padding({ top: 16, left: 10 })
.listDirection(Axis.Horizontal) //横向排布
.width('100%') // 跟随父组件变化
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width(this.rate * 100 + '%') // 宽度变化
this.slider()
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
}
Row() {
Scroll() {
Row({ space: 10 }) {
ForEach(this.appList, () => {
Column() {
CustomBuild() // 自定义组件
}
.width(80)
.height(102)
}, (item: number, index: number) => item.toString() + index)
}
.padding({ top: 16, left: 10 })
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.scrollable(ScrollDirection.Horizontal)
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
}
1.1.6 延伸 > displayPriority(1)
- 这玩意数字越大, 显示优先级越高
- image添加也ok
Column() {
Row() {
Row() {
Image($r('app.media.startIcon')) .width(48).height(48) .objectFit(ImageFit.Contain)
}
.displayPriority(1) .padding({ left: 12, right: 12 })
Row() {
Image($r('app.media.startIcon')) .width(48).height(48) .objectFit(ImageFit.Contain)
}
.displayPriority(2) .padding({ left: 12, right: 12 })
Row() {
Image($r('app.media.startIcon')) .width(48).height(48) .objectFit(ImageFit.Contain)
}
.displayPriority(3) .padding({ left: 12, right: 12 })
Row() {
Image($r('app.media.startIcon')) .width(48).height(48) .objectFit(ImageFit.Contain)
}
.displayPriority(2) .padding({ left: 12, right: 12 })
Row() {
Image($r('app.media.startIcon')) .width(48).height(48) .objectFit(ImageFit.Contain)
}
.displayPriority(1) .padding({ left: 12, right: 12 })
}
.width(this.rate * 100 + '%') // 宽度跟随变化
.height(96)
.borderRadius(16)
.backgroundColor($r('sys.color.comp_background_primary'))
.justifyContent(FlexAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor($r('sys.color.background_secondary'))
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
1.1.7 折行 > wrap: FlexWrap.Wrap
- 根据屏幕大小, 自适应是否换行
Flex({ // 默认横轴
alignItems: ItemAlign.Center, // 垂直居中
justifyContent: FlexAlign.Center, // 横向居中
wrap: FlexWrap.Wrap // 自动换行
}) {
ForEach(this.imageList, (item: Resource) => {
Image(item)
.width(192)
.height(138)
.padding(10)
}, (item: Resource, index: number) => item.toString() + index)
}
1.2 页面适配 (响应式布局)
sm: 小屏 md: 中屏 lg: 大屏
1.2.1 重复布局 (List)
- 通过List的的lanes属性设置列数, 动态设置大屏/中屏/小屏的展示几列
- 系统方法 小屏设置1列, 中屏2列
- 自定义方法 小屏2列, 中屏3列
// 系统方法 小屏1行 中屏2行 大屏3行
// 自定义方法 小屏2行 中屏3行 大屏4行
build() {
Column(){
Text( this.breakPoint.widthBreakpoint === WidthBreakpoint.WIDTH_SM ? '小屏':
this.breakPoint.widthBreakpoint === WidthBreakpoint.WIDTH_MD ? '中屏' : '大屏')
List({space: this.breakPoint.widthBreakpoint >= WidthBreakpoint.WIDTH_LG ? 16: 12}){
ForEach(this.array, (item: string, index: number) => {
ListItem(){
this.listItemBuild(item) // 自定义text
}
},(item: string, index: number)=> item)
}
// 设置list行数 (调用系统方法)
// .lanes({fillType: PresetFillType.BREAKPOINT_SM1MD2LG3}, this.breakPoint.widthBreakpoint >= WidthBreakpoint.WIDTH_LG ? 16: 12)
// 设置list行数 (自定义)
.lanes(
this.breakPoint.widthBreakpoint === WidthBreakpoint.WIDTH_SM ? 2 : // 小屏2列
this.breakPoint.widthBreakpoint === WidthBreakpoint.WIDTH_MD ? 3 : 4, // 中屏2列 其他4列
this.breakPoint.widthBreakpoint >= WidthBreakpoint.WIDTH_LG ? 16 : 12 // 第二个餐参数间距
)
}
.size({width: '100%',height: '100%'})
.padding(10)
.backgroundColor(Color.Gray)
}
1.2.2 分栏布局 (Navigation)
- 基于navigation的mode属性来实现页面的
分栏效果, sm小屏Stack模式,中屏大屏 Split 侧栏模式,可以设置左侧栏宽度
// 分栏页 UI
@Builder function PlaceholderPage() {
Column() {
Text('分栏模式占位页')
.fontSize(28)
.fontWeight(700)
.margin({ top: 200 })
}.width('100%')
.height('100%')
}
@Entry
@Component
struct NavigationExample {
// 定义分栏页 UI
placeholder = new ComponentContent(this.getUIContext(),wrapBuilder(PlaceholderPage))
build() {
Navigation() {
}
.mode(NavigationMode.Split) // Split 分栏模式 默认是Stack
.splitPlaceholder(this.placeholder) // 定义分栏
.navBarWidthRange([this.minNavBarWidth, this.maxNavBarWidth]) //1 可以指定左侧栏 导航宽度范围 [最小宽度, 最大宽度]
.minContentWidth(this.minContentWidth)
.navBarWidth(500) // 2 可以指定左侧栏 导航固定宽度 也可以断点判断设置
}
}
1.2.3 挪移布局 (tabs)
- 获取手机断点, 来区分大/小屏, 从而判断tabs导航是否垂直展示
@State curIndex: number = 0
// 2 定义 TabsController
tabController = new TabsController()
@Env(SystemProperties.BREAK_POINT) breakPoint: uiObserver.WindowSizeLayoutBreakpointInfo;
build() {
NavDestination() {
// 中屏及以上 tabs导航在 顶部, 小屏导航在底部
Tabs({barPosition: this.breakPoint.widthBreakpoint >= WidthBreakpoint.WIDTH_MD ? BarPosition.Start: BarPosition.End, controller: this.tabController }){
TabContent() {
Text('首页')
}
.tabBar(this.mainTabbar('首页',0,$r('sys.symbol.house_fill')))
TabContent() {
Text('设置')
}
.tabBar(this.mainTabbar('我的',1,$r('sys.symbol.person_crop_circle_fill_1')))
}
.barHeight(70)
// 中屏及以上 垂直显示, 小屏水平显示
.vertical( this.breakPoint.widthBreakpoint >= WidthBreakpoint.WIDTH_MD ? true: false)
.onChange((index: number) => {
this.curIndex = index
})
}
}
1.2.4 缩进布局
- 通过栅格布局设置缩进
// md缩进1列 lg缩进2列
GridRow({
columns:{sm: 4, md: 8, lg: 12} // GridRow占据的列数
}){
GridCol({
span: {sm: 4, md: 6, lg: 8}, // GridCol 占据的列数
offset: {sm: 0, md: 1, lg: 2} // GridCol 左侧空出的列数
}){
column(){
//.....
}
}
}
1.3 页面适配 (窗口模式)
5.3.1 窗口模式
- 让应用支持, 全屏/分屏及悬浮框, 当用户分屏操作时候, 应用才能支持
// module.json5配置 让当前应用支持, 全屏 分屏 及悬浮框,
"supportWindowMode": ["fullscreen", "split","floating"],
1.3.2 旋转模式
"orientation": "follow_desktop", // 主要用于平板、2in1 等大屏设备上,应用窗口方向跟随系统桌面设置旋转。
| 设备类型 | 推荐配置 |
|---|---|
| 手机应用,固定竖屏 | "portrait" |
| 手机应用,支持旋转 | "auto_rotation" |
| 横屏游戏 | "landscape" |
| 平板/2in1 应用 | "follow_desktop" |
1.3.3 沉浸式
- 参见 沉浸式
1.4 多设备开发 (功能校验 )
- 多设备开发中, 验证某设备是否支持某功能
- 例如: 代码支持phone, tablet, tv 但是仅仅前两者支持定位, 所以需要通过
canIUse判断当前设备是否支持定位功能, tv呢就正常提示不支持定位功能~
// 判断设置是否支持 NFC
if (canIUse('SystemCapability.Communication.NFC.Core')) {
hilog.info(0x0000, 'Index', `该设备支持SystemCapability.Communication.NFC.Core`);
} else {
hilog.error(0x0000, 'Index', `该设备不支持SystemCapability.Communication.NFC.Core`);
}
二 三层架构 (多端部署)
# 通过ide创建三层架构模式,其实就是新增n个独立的模块处理业务.
- common 放公共能力,谁都能调,不依赖别人 (请求类 解析类 工具类等)
- feature 放业务功能,依赖 common 的公共能力 (首页, 个人中心等)
- entry 放入口和路由,把 feature 和 common 装配起来,最终打包成可安装的应用
# product文件夹目前只有一个phone, 可以新增tablet(平板)或只能穿戴等, 多端开发
2.1 三层架构模式
-
一套代码多端部署的架构, 适用于中大型项目
-
创建路径 创建项目 - Flexible Layout Ability
2.2 hap har hsp包介绍
.HAP 包(应用安装包基本单元)
- entry:应用入口主模块,必须有且仅有一个
- feature:动态特性模块,按需加载,支持选择性安装(如穿戴、AI 调用等场景)
- 本身可以独立安装运行
.HAR 包(静态共享包)
- 提供可复用的代码和资源,编译时拷贝到每个引用模块中
- 本身无法独立安装运行
- 优势:无运行时加载开销,启动更直接
- 劣势:每个引用模块生成一份副本,包体积更大
- 适用:网络请求、数据加解密等通用工具库,封装在 common 层供各模块调用
.HSP 包(动态共享包)
- 提供可复用的代码和资源,运行时动态加载
- 本身无法独立安装运行
- 优势:一份资源多模块共用,包体积更小
- 劣势:首次加载有额外开销,加载完成后性能与 HAR 基本相当
- 适用:多模块共用的 UI 组件库、大型共享资源等
2.3 不同模块怎么调用
// 当前模块 oh-package.json5中引入依赖
// 例如当前模块 引入 common模块
// 与引入三方库类似
"dependencies": {
'@ohos/common': 'file:../common'
}
2.4 模块课单独打包
ide -> 选中模块 -> 构建(默认会提示当前构建模块)
2.5 一多应用上架
⚠️⚠️⚠️ 已经发布的设备类型只增不减少
# 已发布的应用支持 手机 平板, 则二次升级不能仅支持手机 注意发布支持类型与项目内保持一致
# 首次发布务必 仔细查看支持安装类型 !!!
三 启动页
3.1 简易启动页
// 配置 简易启动页 图标及背景色
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility1.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon", // 启动页图标
"startWindowBackground": "$color:start_window_background", // 启动页背景 当前'#FFFFFF'
"exported": true,
}
],
// 如果按照sm屏切图直接替换startWindowIcon, 会有问题!
// 折叠屏展开后, 启动页面的显示是两侧留白
3.2 增强启动页
- 还是很固定 不够灵活
// 1 新建启动页增强文件 start_window.json (resource/profile中新增)
{
"startWindowBackgroundColor": "$color:start_window_background", // 背景色
"startWindowAppIcon": "$media:app_icon", // 图标
"startWindowBrandingImage": "$media:brand" // 品牌logo
}
// 2 mudule.json5 关联新建增强启动页
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility1.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"startWindow": "$profile:start_window", // 关联增强启动页配置
}
],
3.2 自定义启动页
### 处理策略
**启动图标**:设成透明图或 1x1 透明像素,让系统启动页看不到图标。
**背景颜色**:设成和你自定义启动页**底色一致**的颜色,这样系统启动页和自定义启动页之间没有颜色跳变。