电子说
随着智能设备类型的不断丰富,用户可以在不同的设备上享受同样的服务,但由于设备形态不尽相同,开发者往往需要针对具体设备修改或重构代码,以实现功能完整性和界面美观性的统一。OpenHarmony为开发者提供了“一次开发,多端部署”的系统能力,让开发者可以基于一次开发,快速构建不同类型终端上的应用,降低开发成本,提高开发效率。
本篇Codelab基于“一次开发,多端部署”提供的自适应布局和响应式布局能力,实现了常见的视频播放应用的主界面。通过三层工程结构尽可能复用了部分代码,并根据设备尺寸的区别设计了对应的页面以兼顾美观和易用。应用被打开时会根据具体的设备形态显示对应的UI界面,其中RK3568开发板的首页效果如图所示:
完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:
gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
点击或者复制转到。本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。
“一次开发,多端部署”推荐使用三层目录的工程结构来管理工程,上层目录包括common、features和product,common为公共特性目录,存放不同形态设备公用的类和常量,features为功能模块目录,存放应用的各个功能模块,product为产品层目录,存放不同形态设备范类代码。本Codelab不涉及功能特性,因此只存在common、product两个分层。
├──common // 公共能力层
│ ├──src/main/ets
│ │ ├──constants
│ │ │ └──CommonConstants.ets // 公共常量类
│ │ ├──utils
│ │ │ └──BreakpointSystem.ets // 断点工具类
│ │ └──viewmodel // 资源类接口
│ │ ├──BottomTabsItem.ets
│ │ ├──DriveTabsItem.ets
│ │ ├──FindTabsItem.ets
│ │ ├──HomeTabsItem.ets
│ │ └──MineTabsItem.ets
│ └──src/main/resources // 资源文件夹
└──product // 产品定制层
├──default/src/main/ets // 支持手机(含折叠屏)、平板
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口类
│ ├──pages
│ │ └──MainPage.ets // 主页面
│ ├──view
│ │ ├──BottomTabsComponent.ets // 底部页签组件
│ │ ├──DriveTabsComponent.ets // 云盘页组件
│ │ ├──FindTabsComponent.ets // 发现页组件
│ │ ├──HomeTabsComponent.ets // 首页组件
│ │ ├──LeftTabsComponent.ets // 侧边栏组件
│ │ ├──MineTabsComponent.ets // 个人页组件
│ │ ├──RecentlyPlayedComponent.ets // “最近播放”列表
│ │ └──RecommendComponent.ets // “为你推荐”列表
│ └──viewmodel
│ ├──BottomTabsModel.ets // 底部页签model
│ ├──DriveTabsModel.ets // 云盘页model
│ ├──FindTabsModel.ets // 发现页model
│ ├──HomeTabsModel.ets // 首页model
│ └──MineTabsModel.ets // 个人页model
└──default/src/main/resources // 资源文件夹
`HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿`
为了操作便捷和充分利用不同形态设备的屏幕空间,按屏幕宽度的大小将设备划分为3类:
根据用户使用场景,当操作设备尺寸为sm或md时,一般为竖向使用,此时用于切换应用页面的页签栏适合置于底部。当操作设备尺寸为lg时,一般为横向使用,此时页签栏适合置于左侧。
// MainPage.ets
@Entry
@Component
struct MainPage {
...
build() {
SideBarContainer(SideBarContainerType.Embed) {
LeftTabs({ bottomTabIndex: $bottomTabIndex }); // 侧边栏
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.End, justifyContent: FlexAlign.End }) {
Tabs({ barPosition: BarPosition.End, index: 0, controller: this.controller }) {
... // 页面内容
}
if (this.currentBreakpoint !== Const.LG) {
BottomTabs({ bottomTabIndex: $bottomTabIndex }) // 底部栏,当屏幕尺寸不为"lg"时显示
}
}
.width(Const.FULL_SIZE)
.backgroundColor($r('app.color.background_color'))
}
.showSideBar(this.currentBreakpoint === Const.LG) // 当屏幕尺寸为"lg"时显示侧边栏
.showControlButton(false)
.sideBarWidth(Const.SIDEBAR_WIDTH)
.maxSideBarWidth(Const.SIDEBAR_WIDTH_MAX)
.minSideBarWidth(Const.SIDEBAR_WIDTH_MIN)
}
}
首页显示轮播图和“最近播放”、“为你推荐”两个列表,轮播图根据屏幕尺寸的区别,有显示数量的不同(sm为1,md为2,lg为3),列表使用具备自适应布局能力的List组件。
// HomeTabsComponent.ets
@Component
export struct HomeTabs {
@Link currentBreakpoint: string;
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 设置sm、md和lg的布局列数分别为4、8、12
columns: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 },
gutter: { x: $r('app.float.gutter_home') },
breakpoints: { value: [Const.BREAKPOINTS_SM, Const.BREAKPOINTS_MD, Const.BREAKPOINTS_LG] }
}) {
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
... // 标题
}
.height($r('app.float.title_height'))
.margin({ bottom: $r('app.float.home_margin1') })
// 搜索栏在sm、md下占满全部列,在lg下占8列
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_8 } }) {
... // 搜索栏
}
.height($r('app.float.home_grid_height1'))
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
Swiper() {
...
}
.height($r('app.float.home_swiper_height'))
.itemSpace(Const.ITEM_SPACE)
// 根据屏幕尺寸大小选择不同的轮播图数量
.displayCount(this.currentBreakpoint === Const.LG ?
Const.NUM_3 : (this.currentBreakpoint === Const.MD ? Const.NUM_2 : Const.NUM_1))
}
.height($r('app.float.home_grid_height2'))
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
... // ”最近播放”列表
}
.height($r('app.float.home_grid_height3'))
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
... // ”为你推荐”列表
}
.height($r('app.float.home_column_height'))
}
.height(Const.FULL_SIZE)
}
...
}
}
发现页使用栅格布局实现“一次开发,多端部署”能力,把sm设置为4列,md设置为8列,lg设置为12列。热播榜单在不同设备尺寸上分别占据4列、6列和8列。
// FindTabsComponent.ets
@Component
export struct FindTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 设置sm、md和lg的布局列数分别为4、8、12
columns: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 },
gutter: { x: $r('app.float.gutter_find') },
breakpoints: { value: [Const.BREAKPOINTS_SM, Const.BREAKPOINTS_MD, Const.BREAKPOINTS_LG] }
}) {
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
... // 标题
}
.height($r('app.float.title_height'))
LazyForEach(new FindDataSource(FindTabsList), (item: FindTabsItem) = > {
// 设置热播榜单在sm、md和lg上分别占据4、6、8列,并且设置offset属性保证在不同设备形态上都能保持居中
GridCol({
span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_6, lg: Const.GRID_8 },
offset: {
md: FindTabsList.indexOf(item) === Const.OFFSET_0 ? Const.OFFSET_1 : Const.OFFSET_2,
lg: FindTabsList.indexOf(item) === Const.OFFSET_0 ? Const.OFFSET_2 : Const.OFFSET_4
}
}) {
... // 榜单内容
}
}, (item: FindTabsItem) = > JSON.stringify(item))
}
}
...
}
}
RK3568开发板上发现页的实际效果如图所示:
云盘页的栅格划分和发现页相同,但是每个子组件在所有屏幕尺寸上都只占据2列。
// DriveTabsComponent.ets
@Component
export struct DriveTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 设置sm、md和lg的布局列数分别为4、8、12
columns: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 },
gutter: { x : $r('app.float.gutter_drive') },
breakpoints: { value: [Const.BREAKPOINTS_SM, Const.BREAKPOINTS_MD, Const.BREAKPOINTS_LG] }
}) {
GridCol({ span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 } }) {
... // 标题
}
.height($r('app.float.title_height'))
ForEach(DriveList, (item: DriveTabsItem) = > {
// 设置云盘内容在sm、md和lg上均占据2列
GridCol({ span: { xs: Const.NUM_2, sm: Const.NUM_2, md: Const.NUM_2, lg: Const.NUM_2 } }) {
... // 云盘内容
}
}, (item: DriveTabsItem) = > JSON.stringify(item))
}
}
...
}
}
RK3568开发板上云盘页的实际效果如图所示:
个人页的栅格划分仍然和发现页相同,但子组件在sm、md形态下占满全部列,在lg形态下只占据8列。
// MineTabsComponent.ets
@Component
export struct MineTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 设置sm、md和lg的布局列数分别为4、8、12
columns: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_12 },
gutter: { x: $r('app.float.gutter_mine') },
breakpoints: { value: [Const.BREAKPOINTS_SM, Const.BREAKPOINTS_MD, Const.BREAKPOINTS_LG] }
}) {
// 设置个人页在sm和md上占满全部列,在lg上占8列,为保证居中在lg上设置offset为2列
GridCol({
span: { xs: Const.GRID_4, sm: Const.GRID_4, md: Const.GRID_8, lg: Const.GRID_8 },
offset: { lg: Const.OFFSET_2 }
}) {
... // 个人页内容
}
}
.height(Const.FULL_SIZE)
.backgroundColor($r('app.color.mine_background_color'))
}
...
}
}
RK3568开发板上个人页的实际效果如图所示:
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !