HarmonyOS鸿蒙Next代码工坊适配Pura X Max实践

HarmonyOS鸿蒙Next代码工坊适配Pura X Max实践

一、概述

HMOS代码工坊是华为官方大型开源示范应用,旨在汇集华为精选的高质量代码案例,覆盖多种开发场景,全面展示鸿蒙应用架构的最佳实践。代码工坊不仅支持1+8多设备运行,涵盖直板机、折叠机、平板、PC、手表等各种设备形态,还全方位体现鸿蒙应用的精致、流畅、智能、易用、安全、全场景互联等特点,并持续迭代鸿蒙新特性。

开发者可以通过应用市场下载体验各种鸿蒙能力和组件样式:

img

也可以下载源码学习鸿蒙开发:https://gitcode.com/HarmonyOS_Samples/sample_in_harmonyos

近期,华为推出了全新Pura X Max折叠手机,配备了一块宽高比10:14的较宽外屏(折叠态)和一块宽高比14:10的内屏(展开态),其折叠态外屏较直板机更宽,而展开态内屏则相较平板更窄,呈现出"宽手机"+"窄平板"的全新设备形态组合。这种创新设计在提升用户体验的同时,也为应用的开发与适配带来了新的挑战。

作为多设备适配的排头兵,HMOS代码工坊也在适配过程中遇到了诸多问题与挑战,经过深入分析与逐一解决,我们总结了一系列适配经验,希望这篇总结能为广大开发者带来一些启发和指导,助力大家更从容地应对鸿蒙生态日益丰富的设备形态。

img

二、新设备概览

首先让我们先了解下新设备的各种硬件参数,Pura X Max设备和Pura X设备同为阔折叠类型,在折叠态拥有和Pura X展开态相近的设备尺寸,其具体参数如下:

折叠态示意图
屏幕方向Orientation 竖屏PORTRAIT
屏幕ID 0
分辨率(px) 1264*1848
分辨率(vp) 459*672
横纵断点 横向断点sm,纵向断点lg

img

同时Pura X Max设备在展开态则进入到接近平板尺寸的lg横向断点下,具体参数如下:

展开态示意图
屏幕方向Orientation 反向横屏LANDSCAPE_INVERTED
屏幕ID 0
分辨率(px) 2584*1828
分辨率(vp) 939*664
横纵断点 横向断点lg,纵向断点sm

img

从设备形态来看,我们可以发现Pura X Max设备在折叠态和直板机位于同一断点区间,展开态则与平板位于同一断点区间,但其具体的宽高比例却又有所不同。若直接套用直板机和平板上的布局策略,可能对布局完整性和沉浸式效果的体验造成影响。在HMOS代码工坊的实际开发中,就遇到了诸如沉浸式头图比例错误、组件卡片内容被截断、卡片分列效果失调等一系列问题。而接下来,我们将通过分析和解决这些实际问题,为大家带来具体的Pura X Max适配指导。

三、适配实操

问题一:沉浸式banner轮播图比例问题

1. 问题现象

在Pura X Max折叠态竖屏形态下,HMOS代码工坊各个tab栏首页顶部banner轮播图高度过高,直接覆盖全屏,下图以组件库首页效果为例对比Pura X Max与直板机的效果:

直板机(以mate60 pro为例) Pura X Max
img img

这样的布局效果导致用户完全看不到下方组件卡片标题,四个tab页面仅展示轮播图内容无法区分具体tab功能,容易引起困惑。

2. 问题根因

首先,我们需要找到轮播图布局尺寸的相关代码,在BaseHomeViewModel.ets文件中,针对横向sm断点的场景,Banner图的宽度设置为100%,即占满全屏,而高度则根据设备宽度的固定比例进行设定。

在这样的布局策略下,对于窄屏设备(宽高比小于1:2)banner图的高度设置为设备宽度的1.28倍,整体布局大约占据页面的60%。然而,对于Pura X展开态和Pura X Max折叠态这样的宽屏设备(宽高比介于9:10.8到9:18之间),Banner图的高度几乎与屏幕高度一致,布局效果过高。

在之前的适配过程中,我们通过deviceInfo.productSeries对Pura X设备进行了特殊处理,将高度比例手动调整为0.58倍,但这种基于特定型号的适配方案缺乏扩展性和兼容性,因此在新的宽屏设备场景下,问题再次出现。这也促使我们进一步思考如何构建一套更具通用性的适配解决方案。

public constructor(initialState: T) {
  // 不建议做法-通过具体设备型号作区分
  this.state.bannerState.bannerHeight = new BreakpointType({
    sm: deviceInfo.productSeries === ProductSeriesEnum.VDE ?
      CommonConstants.BANNER_ASPECT_VERDE * globalInfoModel.deviceWidth :
      CommonConstants.BANNER_ASPECT_SM * globalInfoModel.deviceWidth,
    md: CommonConstants.BANNER_ASPECT_MD * globalInfoModel.deviceWidth,
    lg: BANNER_HEIGHT_LG + naviTitleHeight,
    xl: BANNER_HEIGHT_LG + naviTitleHeight,
  }).getValue(globalInfoModel.widthBreakpoint);
  // ...
}

3. 解决方案

(1) 基础兜底

在上一章根因分析中,我们发现Banner图采用的是固定宽高比的宽度自适应布局,导致在宽屏设备上高度过高,因此为了保证一个兜底的布局效果,我们可以在原有方案中引入一些布局限制:

以最初为直板设备设计的布局为例,较为和谐的布局效果是Banner图占据页面的约60%高度。为此,我们可以使用 constraintSize 属性,设置 maxHeight: '60%',从而限制其高度不会随着宽度的增加而无限制扩大。类似地,还可以设置其他最大或最小的宽高限制,使组件在预设的范围内进行自适应调整。

这样的方案能够有效控制组件的尺寸,避免在不同设备上出现过大或过小的情况。但需要注意的是,单纯依赖尺寸限制也只能保证兜底的效果,在超出限制的设备上都会按照最大值显示,仍可能影响用户体验的一致性。

BannerSwiperBuilder() {
  Swiper(this.swiperController) {
    LazyForEach(this.bannerState.bannerResource, (bannerData: BannerData) => {
      BannerItem({ bannerData: bannerData, widthBreakpoint: this.globalInfoModel.widthBreakpoint })
        .onClick(() => {
          this.handleItemClick?.(bannerData);
        })
    }, (bannerData: BannerData) => bannerData.id.toString())
  }
  // 设置尺寸限制
  .constraintSize({ maxHeight: '60%' })

(2) 细分设计

因此除了设置宽高限制的基础方案外,我们还可以进一步根据具体的设备形态进行细分设计。例如,针对窄屏直板机,我们可以为 Banner 图采用 1:1.28 的竖版长图效果,而在宽屏设备上则切换为 1:0.58 的宽图布局,具体的宽高比例开发者可根据实际需求灵活调整。

在这样的布局策略下,当我们在 sm 断点下的横向场景进行适配时,还需要进一步区分常规直板机与 Pura X Max 折叠屏设备的折叠态。此时,我们可以通过设备宽高比的条件判断来进行分类。将宽高比在 9:18 以内的设备划分为窄屏设备(narrow),包括常规直板机以及双折叠、三折叠设备的折叠态;而 Pura X Max 折叠态外屏、Pura X 展开态内屏等竖屏设备(portrait)的宽高比则在 9:10.8 到 9:18 之间。此外,Pura X 折叠态外屏这类方形屏设备的宽高比则落在 9:7.2 到 9:10.8 的区间内。

参考这个划分策略,我们在屏幕尺寸监听的数据模型中增加一项宽高比。在HMOS代码工坊中,我们通过一个全局变量globalInfoModel记录各种屏幕属性包括横向断点、状态栏高度、折叠状态等,这里我们新增一条aspectRatio记录当前屏幕宽高比。

export class GlobalInfoModel {
  public foldExpanded: boolean = false;
  public widthBreakpoint: WidthBreakpoint = WidthBreakpoint.WIDTH_SM;
  public heightBreakpoint: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM;
  public naviIndicatorHeight: number = 0;
  public statusBarHeight: number = 0;
  public decorHeight: number = 0;
  public deviceHeight: number = 0;
  public deviceWidth: number = 0;
  public needDynamicHideBar: boolean = false;
  // 屏幕宽高比
  public aspectRatio: number = 0;
}

WindowUtil.ets中,通过on(‘windowSizeChange’)监听窗口尺寸的改变,在更新宽高断点的同时实时更新窗口的宽高比。

public static setWindowSize(windowSize?: window.Size | window.Rect) {
  const globalInfoModel: GlobalInfoModel = AppStorage.get(StorageKey.GLOBAL_INFO) ?? new GlobalInfoModel();
  // 监听计算屏幕宽高比
  if (windowSize) {
    const vpHeight = WindowUtil.uiContext.px2vp(windowSize.height);
    const vpWidth = WindowUtil.uiContext.px2vp(windowSize.width);
    globalInfoModel.deviceHeight = vpHeight;
    globalInfoModel.deviceWidth = vpWidth;
    if (vpHeight > 0) {
      globalInfoModel.aspectRatio = vpWidth / vpHeight;
    }
  }
  // ...
}

设置Banner头图布局高度的时候,在sm断点下根据设备宽高比而非具体设备类型进行区分,分别对窄屏设备与宽屏设备设为不同的值。

public constructor(initialState: T) {
  // 通过横向尺寸+宽高比综合区分布局
  this.state.bannerState.bannerHeight = new BreakpointType({
    sm: globalInfoModel.aspectRatio < CommonConstants.ASPECT_SQUARE_PORTRAIT &&
      globalInfoModel.aspectRatio > CommonConstants.ASPECT_PORTRAIT_NARROW ?
      CommonConstants.BANNER_ASPECT_PORTRAIT * globalInfoModel.deviceWidth :
      CommonConstants.BANNER_ASPECT_SM * globalInfoModel.deviceWidth,
    md: CommonConstants.BANNER_ASPECT_MD * globalInfoModel.deviceWidth,
    lg: BANNER_HEIGHT_LG + naviTitleHeight,
    xl: BANNER_HEIGHT_LG + naviTitleHeight,
  }).getValue(globalInfoModel.widthBreakpoint);
  this.state.bannerHeight = this.state.bannerState.bannerHeight;
  this.originBannerHeight = this.state.bannerHeight;
  this.setTitleBarStyle();
}

最终实现的适配效果如下所示:

适配前 适配后
img img

(3) banner图技巧

通过上述场景我们可以看到,Banner图在不同设备上的布局尺寸变化较大。如果为所有设备使用同一张图片,可能会导致图片拉伸失真或出现黑边;而若为每种设备单独制作图片,又会显著增加设计和开发的工作量。

为了解决这一问题,HMOS 代码工坊采用了“一张大图多次复用”的策略:将Banner的核心内容居中设计,并通过颜色延伸处理背景。在不同设备上,使用 objectFit(ImageFit.Cover) 属性对图片进行居中截取,从而在保证视觉一致性的同时,兼顾适配效率和资源优化。

ImageComponent({
  src: this.bannerData?.mediaUrl,
  // 设置图片适应策略
  objectFit: ImageFit.Cover,
  alt: $r(undefined),
})

这样保证图片核心内容正确展示,随设备形态变化的图片比例只会影响背景延伸色块的截取范围。最终效果如下:

banner轮播图原图 直板机 Pura X Max展开态竖屏 平板
img img img img

4. 经验小结

(1) 在使用自适应布局时,组件应结合合理的约束条件进行设计,不能无限制地随着设备尺寸变化而变化。例如,头图应保持合理的页面占比,分列卡片应设置最小宽度和高度等,以确保在不同设备上依然具备良好的视觉效果和可用性。

(2) UX设计中,仅依赖横向断点无法覆盖所有页面场景。在固定高度或特定宽高比的场景下,还需综合考虑纵向断点和宽高比等因素。同时,应尽量避免以具体设备类型作为划分依据,以免影响未来同种设备形态下新设备的兼容性。

问题二:卡片标题文本超长导致布局错乱

1. 问题现象

在组件库首页、样例首页等页面中,卡片的标题文本过长,出现换行或超出边界的情况,进而导致布局错乱。

组件库首页图文卡片 样例首页文章卡片
img img

2. 问题根因

此类问题的根源较为明显:在展开态横屏模式下,Pura X Max设备的尺寸处于lg横向断点,HMOS 代码工坊卡片默认采用三列布局。然而,Pura X Max 设备屏幕宽度较平板稍窄,导致每列卡片宽度受限,标题文本若未做行数限制和超长处理策略,则会引发布局异常。

3. 解决方案

(1) 组件库首页卡片:在组件库卡片中,主副标题使用column组件包裹,采用上下布局。为了保持所有卡片的对齐效果,卡片的高度被固定,因此文本一旦超长后不能换行,必须强制保持为单行展示,超长部分则采用省略处理。可以通过设置maxLines和textOverflow属性实现,代码如下:

Text(this.componentCardData?.subTitle)
  .fontSize($r('sys.float.Body_S'))
  .fontColor($r('sys.color.font_secondary'))
  .fontWeight(FontWeight.Regular)
  .margin({ left: $r('sys.float.padding_level8'), bottom: $r('sys.float.padding_level2') })
  .maxLines(1)
  .ellipsisMode(EllipsisMode.END)
  .textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.componentCardData?.title)
  .fontSize($r('sys.float.Title_M'))
  .fontColor($r('sys.color.font_primary'))
  .fontWeight(FontWeight.Bold)
  .margin({ left: $r('sys.float.padding_level8'), bottom: $r('sys.float.padding_level4') })
  .textOverflow({ overflow: TextOverflow.Ellipsis })
  .maxLines(1)
  .ellipsisMode(EllipsisMode.END)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

(2) 样例首页卡片:样例首页卡片中,主副标题的父容器为Row组件,采用左右布局,与组件库首页卡片的场景略有差异,在同样给主副标题补充设置行数上限与超长省略属性的同时,为了保证主副标题分列卡片两端,需要用到弹性布局中的justifyContent(FlexAlign.SpaceBetween)样式,并使用margin属性添加16vp的安全距离,通过flexShrink属性控制主标题优先省略。

Row() {
  Text(this.discoverContent.title)
    .fontColor($r('sys.color.font_primary'))
    .fontSize($r('sys.float.Body_L'))
    .fontWeight(FontWeight.Bold)
    .maxLines(1)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
    .flexShrink(1)
    .margin({ right: 16 })
  Text(this.discoverContent.subTitle)
    .margin({ bottom: $r('sys.float.padding_level1') })
    .fontColor($r('sys.color.font_primary'))
    .fontSize($r('sys.float.Caption_M'))
    .fontWeight(FontWeight.Regular)
    .maxLines(1)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
    .flexShrink(0)
}

(3) 最终的适配效果如下,文本换行导致截断或者超出组件边界的问题得到了有效地解决:

组件库首页 样例首页文章卡片
img img

4. 经验小结

(1) 对于 image、text 等组件,其内容由具体资源文件决定,可能会超出组件本身设定的尺寸限制,从而导致截断、换行等非预期显示效果。因此,在设计时应合理设置 objectFitmaxLinestextOverflow 等约束属性,以确保在各种边界场景下仍能保持正确的布局效果,多处使用时可以封装成公共自适应组件,如标题类文本统一设置为单行显示超长省略的效果,避免多处代码冗余以及可能的多设备适配遗漏。

(2) 在需要组件尺寸自适应的场景中,建议采用盒子布局等策略,通过合理控制间距与边距来灵活排列子组件,避免使用从左到右的线性布局或固定位置布局。同时,可灵活运用 flex 等自适应布局方式,提升页面在不同设备上的兼容性,具体可参考《自适应布局》文档。

问题三:组件库卡片过窄

1. 问题现象

在统一为问题二添加超长省略逻辑后,组件库首页卡片的分列显示效果仍不理想。由于采用三列布局,标题文本被大量省略,导致用户难以识别各卡片的具体内容,进而影响整体的使用体验。

组件库首页
img

2. 问题根因

在Pura X Max展开态横屏状态下,屏幕宽度为 939vp,左侧侧边栏宽度为 96vp,卡片两侧边距均为32vp,因此实际可用布局宽度仅为 789vp。从实际布局范围来看,卡片区域宽度已处于md断点区间,若继续沿用lg断点下的三列布局,显然效果不协调。此类实际可用空间与窗口尺寸不匹配,且涉及断点阈值边界的情况,需重新审视并调整布局策略。

3. 解决方案

如上所述,由于Tab栏的占用,卡片布局的可用宽度受限。为了使组件库卡片更充分地利用页面空间,我们建议采用API23新推出的悬浮Tab栏能力,为页面带来更沉浸的填充效果和更空间化的设计体验。

具体实现上,可以通过HdsTabs组件的barFloatingStyle属性,将Tab栏设置为半透明悬浮样式,使其悬浮于页面之上,从而释放下方空间,使卡片能够占满全屏。

在使用过程中,请注意做好版本隔离,确保在 API23 以下版本中不会因功能不兼容导致异常。

// 处理版本隔离
if (HdsUtil.isSupportNewMaterialApi()) {
  (this.tabsModifier as HdsTabsModifier)
    .vertical(false)
    .barPosition(BarPosition.End)
    .barHeight(this.globalInfoModel.widthBreakpoint === WidthBreakpoint.WIDTH_XL ? 0 :
      CommonConstants.TAB_BAR_HEIGHT)
    .barFloatingStyle({
      adaptToHandedness: true,
      barBottomMargin: this.globalInfoModel.naviIndicatorHeight,
      systemMaterialEffect: {
        materialType: hdsMaterial.MaterialType.IMMERSIVE,
        materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE,
      },
    })
}

具体效果如下:

组件库首页
img

4. 经验小结

(1) 在设计页面布局时,应关注响应式布局中组件的布局空间与窗口尺寸的对应关系,建议以实际布局范围为依据进行属性设置,确保布局效果更贴合组件实际显示空间。

(2) 针对每个断点区间设计UX时,可借助自由多窗能力检视不同场景下的页面呈现效果。特别需要注意断点之间的边界情况,及时调整布局策略,或在必要时自定义断点划分体系,以提升整体适配性与用户体验。

四、总结

以上是HMOS代码工坊在适配Pura X Max设备过程中遇到的主要问题。通过总结可以发现,问题的根源主要集中在三个方面:自适应布局策略、响应式布局策略以及布局约束。

  • 自适应布局策略:在处理组件尺寸自适应时,应选择合适的变化策略,如弹性布局、按比例划分等,确保组件大小能够随着设备窗口尺寸的变化而合理调整,审视固定数值尺寸的合理性;可以封装一些待自适应属性的自定义Text、Image等组件,供各处使用以防适配遗漏。
  • 响应式布局策略:在基于断点的布局设计中,需特别关注断点区间的划分粒度和阈值设置,确保其符合UX设计需求。同时,应充分验证断点边界条件,并检查不同宽高比下的布局表现。建议根据实际布局空间灵活设定响应式断点,而非依赖固定设备类型。
  • 布局约束:在自适应+响应式的基础上,还需引入必要的布局约束,防止组件无限制地扩展或收缩。尤其对于文本和其他内容尺寸不固定的组件,应设置行数限制、超长策略等约束属性,以确保内容始终能正确展示,不因尺寸变化而错乱。

遵循以上策略进行页面设计和实现,有助于打造更具扩展性和兼容性的UI,使应用在面对日益多样的设备形态时,依然保持稳定、一致的用户体验——真正实现“以不变应万变”。

五、参考链接

HMOS代码工坊源码:https://gitcode.com/HarmonyOS_Samples/sample_in_harmonyos

HMOS代码工坊下载链接:

img

《自适应布局》:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-multi-device-adaptive-layout


更多关于HarmonyOS鸿蒙Next代码工坊适配Pura X Max实践的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

针对Pura X Max的折叠屏/大屏适配,需在代码工坊项目中应用ArkUI响应式布局:利用breakpointSystem监听窗口断点,配合GridRow/GridCol自适应网格;使用onSizeChange实时调整组件宽高;采用vp/fp单位适配不同DPR;针对折叠态与展开态分别配置布局模板,确保界面元素完整显示且交互无障碍。

更多关于HarmonyOS鸿蒙Next代码工坊适配Pura X Max实践的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这篇文章围绕 HarmonyOS 代码工坊适配 Pura X Max 的实践,核心在于解决“宽手机+窄平板”新形态带来的布局挑战。关键经验可归纳为三点:

  1. 自适应策略需加入设备约束:不能仅依赖固定宽高比让组件无限变化。对于沉浸式头图等场景,应结合 constraintSize 限制最大尺寸,或根据屏幕宽高比细分设计,避免用具体设备型号做特殊判断,以提升扩展性。
  2. 响应式布局要审视实际可用空间:断点划分不能只看屏幕尺寸,需考虑侧边栏、Tab 栏等占据的空间。在边界区域,要及时调整列数或利用悬浮 Tab 等新 API 释放布局空间,确保组件在所在区域内的展示效果理想。
  3. 为动态内容设置兜底约束:对文本、图片等大小可变的内容,务必设置 maxLinestextOverflowobjectFit 等约束属性,防止内容超出或换行破坏布局。这些约束可封装为公共组件,规避多处遗漏。

遵循“自适应变化 + 响应断点 + 边界约束”的复合策略,能有效提升 UI 在多形态设备上的兼容性和一致性。

回到顶部