鸿蒙开发next【长视频】一多开发实例

一多开发实例(长视频)概述

本文从目前流行的垂类市场中,选择长视频行业应用作为典型案例详细介绍[“一多”]在实际开发中的应用。长视频行业应用的核心功能为沉浸式的视频播放和互动,主要包含首页推荐、视频搜索、视频详情、视频评论、全屏播放等。根据这些核心功能,本文选择[首页]、[搜索页]、[视频详情页]和[全屏播放页]作为典型页面进行开发,遵从多设备的“差异性”、“一致性”、“灵活性”和“兼容性”,能够让开发者快速高效地掌握“一多”能力并实现长视频应用的相关功能。

当前系统的产品形态主要有手机、折叠屏、平板和2in1四种,下文的具体实践也将围绕这几种产品形态展开,同时将分别从UX设计、工程管理、页面开发和功能开发四个角度给出符合“一多”的参考样例,介绍“一多”长视频应用在开发过程中的最佳实践。

UX设计

影音娱乐类的多设备响应式设计指南。

工程管理

本章将介绍如何创建“一多”工程及划分目录结构。

创建工程

根据三层架构创建系统工程,先创建出最基本的项目工程,再在基本目录结构的基础上进行修改。

工程结构

开发者在创建“一多”的工程时,会遇到如何划分工程结构目录的问题。考虑到工程的复用性和可维护性,本文以长视频应用为例给出推荐的参考方案。

HarmonyOS的分层架构主要包括三个层次:产品定制层、基础特性层和公共能力层,为开发者构建了一个清晰、高效、可扩展的设计架构

长视频应用根据一多推荐的commons、features、products的”三层工程架构“划分目录。其中四个页面功能不同,互不依赖,根据页面划分为四个features(基础特性层):首页-home、视频搜索页-search、视频详情页-videoDetail和全屏播放页-videoPlayer。公共常量、媒体播放工具以及窗口管理工具等需要被不同页面依赖引用的内容,划分为一个commons(公共能力层):基础能力-base。其中features层不同页面的功能相对独立、互不影响,推荐创建HAR包;commons层存放公共能力类,被features层和products层依赖,推荐创建HAR包。

工程结构如下:

├──commons                                    // 公共能力层
│  ├──base/src/main/ets                       // 基础能力
│  │  ├──constants
│  │  └──utils
│  └──base/src/Index.ets                      // 对外接口类
├──features                                   // 基础特性层
│  ├──home/src/main/ets                       // 首页
│  │  ├──constants
│  │  ├──utils
│  │  ├──view
│  │  └──viewmodel
│  ├──home/src/main/resources                 // 资源文件目录
│  ├──home/src/Index.ets                      // 对外接口类
│  ├──search/src/main/ets                     // 搜索页
│  │  ├──constants
│  │  ├──view
│  │  └──viewmodel
│  ├──search/src/main/resources               // 资源文件目录
│  ├──search/src/Index.ets                    // 对外接口类
│  ├──videoDetail/src/main/ets                // 视频详情页
│  │  ├──constants
│  │  ├──utils
│  │  ├──view
│  │  └──viewmodel
│  ├──videoDetail/src/main/resources          // 资源文件目录
│  ├──videoPlayer/src/main/ets                // 全屏播放页
│  │  ├──constants
│  │  └──view
│  └──videoPlayer/src/main/resources          // 资源文件目录
└──products                                   // 产品定制层
   ├──phone/src/main/ets                      // 支持手机、折叠屏、平板、2in1
   │  ├──entryability
   │  └──pages
   └──phone/src/main/resources                // 资源文件目录

页面开发

本章介绍长视频应用中如何使用“一多”的布局能力,完成页面层级的一套页面、多端适配。同时介绍长视频应用中的[交互开发]和推荐的[资源使用]方式。

首页

长视频应用首页主要发挥推荐精选视频的作用,解决用户想要看视频的核心需求,所以首页内容都围绕这一功能设计。观察首页在2in1上的UX设计图,可以进行如下设计(图中为包括可滑动区域的内容):

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

1

  • 整个页面响应式适配,借助栅格组件能力监听不同断点变化实现不同的布局效果。

  • 首页区域2在小设备上呈两行显示,在中设备和大设备上单行显示,断点变化时切换显示效果。

  • 首页区域3、4使用自适应布局延伸能力随不同设备尺寸延伸或隐藏。

  • 首页区域1,5-8使用响应式布局中的栅格断点系统,根据断点变化切换改变组件内相应的属性实现布局效果。

长视频应用搜索页的8个基础区域介绍及实现方案如下表所示:

区域编号 简介 实现方案
1 [底部/侧边页签] 借助[栅格布局]监听断点变化改变位置。
2 [顶部页签及搜索框] 栅格布局监听断点变化实现折行显示,[List组件]实现延伸能力,layoutWeight实现拉伸能力。
3 [Banner图] [Swiper组件],指定displayCount属性实现延伸能力,设置aspectRatio属性实现缩放能力。
4 图标列表 Swiper组件,指定displayCount属性实现自适应布局延伸能力,设置aspectRatio属性实现缩放能力。
5 [推荐视频] [网格容器],借助栅格组件能力监听断点变化改变列数,设置aspectRatio属性实现缩放能力。
6 新片发布 网格容器,借助栅格组件能力监听断点变化改变列数,设置aspectRatio属性实现缩放能力。
7 [每日佳片] 利用响应式布局的栅格布局,结合[Stack组件]和[Grid组件],设置aspectRatio属性实现缩放能力。
8 往期回顾 响应式布局的栅格布局,设置aspectRatio属性实现缩放能力。

在实际开发中,区域1为外层导航栏,区域2为内层导航栏,区域3-8为并列的首页内容,所以对应的开发顺序为区域1、区域2和区域3-8。另外,为了提升用户的使用体验,首页设计了额外的功能,包括[首页社区页签的沉浸式设计],[2in1首页Banner图的排版创新],[首页推荐视频区域长按预览],[首页推荐视频区域的缩放]。

  • 底部/侧边页签

    底部/侧边页签区域,使用Tabs组件,设置在不同断点下的vertical属性,实现显示在首页的不同位置。在sm和md断点下,页签显示在底部,高度为56vp;在lg断点下页签显示在左侧,宽度为96vp,且页签居中显示。

    示意图如下:

2

// features/home/src/main/ets/view/Home.ets
// 底部/侧边页签区域
Tabs({
  // lg断点时,页签栏在侧边;sm、md断点时,页签栏在底部
  barPosition: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? BarPosition.Start : BarPosition.End
}) {
  // ...
}
// 底部页签大小的变换
.barWidth(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? $r('app.float.bottom_tab_bar_width_lg') :
  CommonConstants.FULL_PERCENT)
.barHeight(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? CommonConstants.FULL_PERCENT :
  (deviceInfo.deviceType === CommonConstants.DEVICE_TYPES[0] ? $r('app.float.tab_size_lg') :
  $r('app.float.tab_size')))
// 设置不同断点下页签的布局模式
.barMode(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? BarMode.Scrollable : BarMode.Fixed,
  { nonScrollableLayoutStyle: LayoutStyle.ALWAYS_CENTER })
// lg断点时为纵向Tabs,sm、md断点时为横向Tabs
.vertical(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG)
  • 顶部页签及搜索框

    不同断点下,顶部页签和搜索框占用不同栅格列数,使用栅格布局实现在sm断点下分两行显示,在md和lg断点下单行显示。根据设计将栅格在sm、md和lg的断点上分别划分为4列、12列、12列。示意图如下:
    3


// features/home/src/main/ets/view/HomeHeader.ets
// 顶部页签及搜索框
build() {
  GridRow({
    columns: {
      // 栅格数4、12、12列
      sm: CommonConstants.GRID_ROW_COLUMNS[2],
      md: CommonConstants.GRID_ROW_COLUMNS[0],
      lg: CommonConstants.GRID_ROW_COLUMNS[0]
    }
  }) {
    GridCol({
      span: {
        // 顶部页签占用4、7、7列
        sm: CommonConstants.GRID_COLUMN_SPANS[5],
        md: CommonConstants.GRID_COLUMN_SPANS[2],
        lg: CommonConstants.GRID_COLUMN_SPANS[2]
      }
    }) {
      this.TopTabBar()
    }

    GridCol({
      span: {
        // 搜索框占用4、5、5列
        sm: CommonConstants.GRID_COLUMN_SPANS[5],
        md: CommonConstants.GRID_COLUMN_SPANS[3],
        lg: CommonConstants.GRID_COLUMN_SPANS[3]
      }
    }) {
      this.searchBar()
    }
  }
}

随着设备宽度变大,顶部页签间距变大、页面能够展示更多页签内容,使用List组件实现延伸能力;同时使用layoutWeight将增加的空间全部分配给搜索框,实现拉伸能力。

// features/home/src/main/ets/view/HomeHeader.ets
// 顶部页签
@Builder
TopTabBar() {
  Row() {
    Column() {
      List({
        // 随着断点变大,页签间距变大
        space: new BreakpointType(HomeConstants.SEARCH_TAB_LIST_SPACES[0], HomeConstants.SEARCH_TAB_LIST_SPACES[1],
          HomeConstants.SEARCH_TAB_LIST_SPACES[2]).getValue(this.currentBreakpoint)
      }) {
        ...
      }
    }
  }
}

// 搜索框
@Builder
searchBar() {
  Row() {
    Stack({ alignContent: Alignment.Start }) {
      // ...
    }
    .alignSelf(ItemAlign.Center)
    // 增加的空间全部分配给搜索框
    .layoutWeight(1)
  }
}
  • Banner图

    Banner图和图标列表区域,均使用Swiper组件,设置在不同断点下的displayCount属性来实现自适应布局的延伸能力,本章节以Banner图区域作为示例,图标列表的实现读者可以自行查看代码。Banner图区域中,Banner展示数量在sm断点下为1,并显示导航点指示器;在md和lg断点下Banner为2,且前后边距展示前后两张Banner图的部分内容。

    在“一多”的应用中,经常会出现窗口大小改变如果组件随着窗口宽度变化只改变宽度、不改变高度,会导致图片变形,视觉上会给用户带来较差体验。为解决这一痛点,需要给Stack组件设置aspectRatio属性,Stack的高度会跟随宽度变化相应等比发生变化,Banner图大小变化且宽高比保持不变,实现自适应布局的缩放能力。

    示意图如下:

4

// features/home/src/main/ets/view/BannerView.ets
// Banner图区域
Swiper() {
  LazyForEach(this.bannerDataSource, (item: Banner, index: number) => {
    Column() {
      Stack() {
        // ...
      }
      .height(item.getBannerImg().getHeight().getValue(this.currentBreakpoint))
      .width(CommonConstants.FULL_PERCENT)
      // 宽高按照预设的比例,随容器组件发生变化且宽高比不变
      .aspectRatio(new BreakpointType(HomeConstants.BANNER_RATIOS[0], HomeConstants.BANNER_RATIOS[1],
        HomeConstants.BANNER_RATIOS[2]).getValue(this.currentBreakpoint))
    }
  }, (item: Banner, index: number) => index + JSON.stringify(item))
}
// ...
.index(2)
// 设置不同断点下的Banner展示数量,实现自适应布局的延伸能力
.displayCount(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? 1 : HomeConstants.TWO)
.itemSpace(HomeConstants.SWIPER_ITEM_SPACE)
// 设置是否显示导航点指示器
.indicator(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? Indicator.dot()
  .itemWidth($r('app.float.swiper_item_size'))
  .itemHeight($r('app.float.swiper_item_size'))
  .selectedItemWidth($r('app.float.swiper_selected_item_width'))
  .selectedItemHeight($r('app.float.swiper_item_size'))
  .color($r('app.color.swiper_indicator'))
  .selectedColor(Color.White) : false
)
.loop(true)
.width(CommonConstants.FULL_PERCENT)
.visibility((this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG) && (this.currentTopIndex === 1) ?
  Visibility.None : Visibility.Visible)
.effectMode(EdgeEffect.None)
// md与lg设备上前后露出两张Banner图的大小不同
.prevMargin(new BreakpointType($r('app.float.swiper_prev_next_margin_sm'),
  $r('app.float.swiper_prev_next_margin_md'), $r('app.float.swiper_prev_next_margin_lg'))
  .getValue(this.currentBreakpoint))
.nextMargin(new BreakpointTy
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值