arkts-10 多端适配/三层架构/启动页

90 阅读2分钟

一 多端适配

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属性设置列数, 动态设置大屏/中屏/小屏的展示几列

截屏2026-09-28 12.14.44.png

  • 系统方法 小屏设置1列, 中屏2列

截屏2026-09-28 12.10.19.png

  • 自定义方法 小屏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 侧栏模式,可以设置左侧栏宽度

截屏2026-09-28 14.09.07.png


// 分栏页 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导航是否垂直展示 截屏2026-09-28 14.33.41.png

@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 缩进布局

  • 通过栅格布局设置缩进 截屏2026-09-28 14.36.07.png

// 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

    截屏2026-09-29 13.25.59.png

截屏2026-09-29 10.06.38.png

截屏2026-09-29 10.25.27.png

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 -> 选中模块 -> 构建(默认会提示当前构建模块)

截屏2026-09-29 13.52.15.png

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 增强启动页

  • 还是很固定 不够灵活 截屏2026-09-29 14.38.49.png

// 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 透明像素,让系统启动页看不到图标。

**背景颜色**:设成和你自定义启动页**底色一致**的颜色,这样系统启动页和自定义启动页之间没有颜色跳变。