• 周五. 4 月 24th, 2026

物嫩软件资讯网

软件资讯来物嫩

鸿蒙开发5.0案例【一多开发(便捷生活)】

admin@wunen

5 月 23, 2025



页面开发

本章介绍便捷生活应用中如何使用“一多”的布局能力,完成页面层级的一套页面、多端适配。下文将从不同页面展开,介绍每个页面区域使用到具体的布局能力,帮助开发者从0到1进行便捷生活应用的开发。



布局能力

本节由不同页面展开,介绍每个页面区域使用到的具体布局能力,帮助开发者从零到一进行便捷生活应用的开发。


首页

首页通常有入口图标和商品卡片等丰富的页面跳转入口和商品推荐信息,帮助解决用户浏览及挑选商品的核心需求。观察首页在不同设备上的UX设计图,可以进行如下设计:

  • 将首页划分为5个区域,效果图如下:

首页的5个基础区域介绍及实现方案如下表所示:


美食列表

美食列表页显示推荐美食,在大屏上增多列数的布局以增加用户信息量获取。观察美食列表页在不同设备上的UX设计图,可以进行如下设计:

  • 将美食列表页划分为3个区域,效果图如下:


店铺页

店铺页展示店铺信息和其所售卖的所有商品,可以在侧边栏查看并且快速切换,用户也可以选择商品规格,在不同产品中弹窗以不同形态显示,贴合操作习惯。观察店铺页在不同设备上的UX设计图,可以进行如下设计:

  • 将店铺页划分为4个区域,效果图如下:

店铺页的4个基础区域介绍及实现方案如下表所示:

  • 店铺信息展示区

    使用Flex属性的direction属性根据断点切换上下或者左右布局,使用visibility属性根据断点切换显隐。

// entry/src/main/ets/pages/ShopDisplay.ets
Flex({
  direction: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG && !this.ifShowSides ?
  FlexDirection.Row : FlexDirection.Column,
  justifyContent: FlexAlign.Start
}) {
  ShopHeader()
    .visibility(this.currentBreakpoint !== BreakpointConstants.BREAKPOINT_LG || this.ifShowSides ?
    Visibility.Visible : Visibility.None)
  ShopSideBar()
    .width(CommonConstants.THIRTY_SEVEN_PERCENT)
    .flexShrink(0)
    .visibility(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG && !this.ifShowSides ?
    Visibility.Visible : Visibility.None)
  ShopOrderList()
    .height(CommonConstants.FULL_PERCENT)
  // ...
}
  • 菜单列表

使用Tabs嵌套Scroll组件实现菜单页签切换。

// entry/src/main/ets/view/ShopOrderList.ets
// Tabs显示上方页签
Tabs({ controller: this.topTabsController }) {
  ForEach(this.tabsList, () => {
    TabContent() {
      ShopMenu().width(CommonConstants.FULL_PERCENT)
    }
  })
}
// entry/src/main/ets/view/ShopMenu.ets
Row() {
  Column() {
    // ...
  }
  // ...
  Scroll(this.scroller) {
    Column(){
      // ...
    }
  }
  // ...
  // 以nestedScroll实现滑动嵌套效果
  .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST })
}
  • 选规格弹窗

    在sm和md时使用bindSheet(半模态转场)组件实现,在lg规格屏幕使用PopUp实现跟手弹窗。

// entry/src/main/ets/view/ShopDish.ets
Text($r('app.string.select_specification'))
  // ...
  .onClick(() => {
    if (this.currentBreakpoint !== BreakpointConstants.BREAKPOINT_LG) {
      this.showPop = true;
    } else {
      this.showPopUp = true;
      this.showPopUpChange = true;
    }
  })
  .bindSheet($$this.showPop, this.popBuilder(), {
    height: SheetSize.FIT_CONTENT,
    backgroundColor: Color.White,
    title: {
      title: $r('app.string.select_specification')
    },
    maskColor: $r('app.color.forty_black')
  })
  .bindPopup(this.showPopUp, {
    builder: this.popBuilder,
    placement: Placement.Left,
    width: $r('app.float.popup_width'),
    mask: { color: $r('app.color.forty_black') },
    onStateChange: (e) => {
      if (!e.isVisible) {
        this.showPopUp = false;
      }
    }
  })


商品详情

商品详情页展示商品具体信息,加入了可以通过上下滑动查看完整商品缩略图的交互设计,商品全貌展现更加直观,也可以使用侧边栏查看商品,交互更加便捷。观察商品详情页在不同设备上的UX设计图,可以进行如下设计:

  • 将商品详情页划分为3个区域,效果图如下:

商品详情页的3个基础区域介绍及实现方案如下表所示:

  • 效果图-交互动画(上下滑动查看完整缩略图)

    通过绑定onScrollFrameBegin监听滑动改变图片高度:

// entry/src/main/ets/pages/DishDetails.ets
@State ifPictureExpansion: Boolean = false;
@State imageHeightExtension: number = 0;
@State imageHeightFold: number = 0;
@State imageHeight: number = 0;
// ...
build() {
  NavDestination() {
    Scroll(this.informationScroller) {
      GridRow({
        columns: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG && !this.ifShowSides ?
        BreakpointConstants.GRID_ROW_COLUMNS[2] : 1
      }) {
        GridCol({
          span: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG && !this.ifShowSides ?
          BreakpointConstants.GRID_COLUMN_SPANS[7] : 1
        }) {
          if (this.currentBreakpoint !== BreakpointConstants.BREAKPOINT_LG || this.ifShowSides) {
            DishHead({
              ifPictureExpansion: this.ifPictureExpansion,
              imageHeightExtension: this.imageHeightExtension,
              imageHeightFold: this.imageHeightFold,
              imageHeight: this.imageHeight
            })
          } else {
            DishSideBar()
          }
        }
        // ...
      }
    }
    // ...
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      if (!this.ifPictureExpansion && offset < 0) {
        this.imageHeight = this.imageHeightExtension;
        this.ifPictureExpansion = true;
        return { offsetRemain: 0 };
      } else if (this.ifPictureExpansion && offset > 0) {
        this.imageHeight = this.imageHeightFold;
        this.ifPictureExpansion = false;
        return { offsetRemain: 0 };
      } else {
        return { offsetRemain: offset };
      }
    })
  }
  // ...
}
// entry/src/main/ets/view/DishInformation.ets
@Link @Watch('resizeImage') ifPictureExpansion: Boolean;
@Link imageHeightExtension: number;
@Link imageHeightFold: number;
@Link imageHeight: number;
@State @Watch('resizeImage') imageRatio: number = 0;
@State @Watch('resizeImage') imageWidth: number = 0;

// 监听ifPictureExpansion改变图片高度
resizeImage(): void {
  this.imageHeightExtension = this.imageWidth * this.imageRatio;
  this.imageHeightFold = Math.min(380, this.imageHeightExtension);
  if (this.ifPictureExpansion) {
    this.imageHeight = this.imageHeightExtension;
  } else {
    this.imageHeight = this.imageHeightFold;
  }
}

build() {
  Column() {
    Stack({
      alignContent: Alignment.TopStart
    }) {
      Image($r('app.media.merchandiseMap'))
        .width(CommonConstants.FULL_PERCENT)
        .height(this.imageHeight)
        .objectFit(ImageFit.Cover)
        // 图片高度变化动画
        .animation({
          duration: 250,
          curve: Curve.EaseOut,
          iterations: 1,
          playMode: PlayMode.Normal
        })
        .onComplete((msg) => {
          this.imageRatio = msg!.height / msg!.width;
        })
        .onAreaChange((oldArea: Area, newArea: Area) => {
          this.imageWidth = Number.parseInt(JSON.stringify(newArea.width));
          if (!this.ifPictureExpansion) {
            this.imageHeight = this.imageHeightFold;
          } else {
            this.imageHeight = this.imageHeightExtension;
          }
        })
      // ...
    }
    // ...
  }
  // ...
}


微详情页

微详情页显示推荐的商品信息,在不同设备上以不同列数呈现,增加页面呈现的信息量。观察微详情页在折叠屏上的UX设计图,可以进行如下设计:

  • 效果图如下:


电影列表页

电影列表页展示推荐的电影信息,我们为lg规格的屏幕提供了三种设计范式,读者可自行选择参考。观察电影列表页页在不同设备上的UX设计图,可以进行如下设计:

  • 将电影列表页划分为3个区域,效果图如下:

电影列表页的3个基础区域介绍及实现方案如下表所示


电影简介页

电影简介页展示电影的具体信息,在lg规格的屏幕上采用了左右布局,以充分利用空间。观察电影简介页在不同设备上的UX设计图,可以进行如下设计:

  • 将电影简介页划分为3个区域,效果图如下:

电影简介页的3个基础区域介绍及实现方案如下表所示


选影院页

选影院页展示影院列表以供用户选择,并提供电影海报预览方便切换。观察选影院页在不同设备上的UX设计图,可以进行如下设计:

  • 将选影院页划分为3个区域,效果图如下:

选影院页的3个基础区域介绍及实现方案如下表所示


首页-推荐页

首页-推荐页展示向用户推荐的图文简略信息,在不同设备上以不同列数呈现,增加信息量呈现,观察首页-推荐页在不同设备上的UX设计图,可以进行如下设计:

  • 将首页-推荐页划分为3个区域,效果图如下:


首页-关注页

首页-关注页展示用户的关注列表和以及其最新发布的图文信息,我们提供了三种范式,读者可自行选择参考。观察首页-关注页在不同设备上的UX设计图,可以进行如下设计:

  • 将首页-关注页划分为4个区域,效果图如下:

首页的4个基础区域介绍及实现方案如下表所示


短视频详情页

短视频详情页进行视频播放,并相关提供功能按钮,提供边看视频边看评论的页面设计。观察短视频页在不同设备上的UX设计图,可以进行如下设计:

  • 将短视频页划分为4个区域,效果图如下:

短视频详情页的4个基础区域介绍及实现方案如下表所示


直播页

直播页进行直播播放并展示用户评论,背景使用图片模糊带给用户更沉浸的观看体验。观察直播页在不同设备上的UX设计图,可以进行如下设计:

  • 将直播页划分为3个区域,效果图如下:
  • 直播区-背景模糊
// entry/src/main/ets/pages/Living.ets
SideBarContainer(SideBarContainerType.Embed) {
  LivingComments()
    .width(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?
    CommonConstants.FORTY_PERCENT_STRING : CommonConstants.THIRTY_SEVEN_PERCENT)
  LivingHome()
}
.width(CommonConstants.FULL_PERCENT)
.backgroundImage($r('app.media.fm2_img'))
// 设置背景模糊
.backgroundBlurStyle(BlurStyle.BACKGROUND_THICK, { colorMode: ThemeColorMode.DARK,
  adaptiveColor: AdaptiveColor.DEFAULT })
.backgroundImageSize({
  width: CommonConstants.FULL_PERCENT,
  height: CommonConstants.FULL_PERCENT
})


图文详情页

图文详情页展示商品具体信息。观察图文详情页在不同设备上的UX设计图,可以进行如下设计:

  • 将图文详情页划分为7个区域,效果图如下:

图文详情页的7个基础区域介绍及实现方案如下表所示

  • 上图下文有三种范式,可以通过调节swiper展示
  • 交互效果
// entry/src/main/ets/view/GraphicTextSwiper.ets
Image(item)
  // ...
  // 点击放大
  .onClick(() => {
    animateTo({
      duration: CommonConstants.ANIMATE_DURATION,
      curve: Curve.Friction
    }, () => {
      this.isFullScreen = true;
      this.fullImageIndex = index;
    });
  })
  // 双指
  .gesture(
    PinchGesture({ fingers: 2 })
      .onActionUpdate((event: GestureEvent) => {
        animateTo({
          duration: CommonConstants.ANIMATE_DURATION,
          curve: Curve.Friction
        }, () => {
          this.isFullScreen = true;
          this.fullImageIndex = index;
        });
      })
  )

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注