介紹
隨著智能設(shè)備類型的不斷豐富,用戶可以在不同的設(shè)備上享受同樣的服務(wù),但由于設(shè)備形態(tài)不盡相同,開發(fā)者往往需要針對(duì)具體設(shè)備修改或重構(gòu)代碼,以實(shí)現(xiàn)功能完整性和界面美觀性的統(tǒng)一。OpenHarmony為開發(fā)者提供了“一次開發(fā),多端部署”的系統(tǒng)能力,讓開發(fā)者可以基于一次開發(fā),快速構(gòu)建不同類型終端上的應(yīng)用,降低開發(fā)成本,提高開發(fā)效率。
本篇Codelab基于“一次開發(fā),多端部署”提供的自適應(yīng)布局和響應(yīng)式布局能力,實(shí)現(xiàn)了常見的視頻播放應(yīng)用的主界面。通過(guò)三層工程結(jié)構(gòu)盡可能復(fù)用了部分代碼,并根據(jù)設(shè)備尺寸的區(qū)別設(shè)計(jì)了對(duì)應(yīng)的頁(yè)面以兼顧美觀和易用。應(yīng)用被打開時(shí)會(huì)根據(jù)具體的設(shè)備形態(tài)顯示對(duì)應(yīng)的UI界面,其中RK3568開發(fā)板的首頁(yè)效果如圖所示:
相關(guān)概念
- [一次開發(fā),多端部署]:指一套代碼工程,一次開發(fā)上架,多端按需部署,目標(biāo)是支撐開發(fā)者高效地開發(fā)支持多種終端設(shè)備形態(tài)的應(yīng)用。
- [自適應(yīng)布局]:當(dāng)外部容器大小發(fā)生變化時(shí),元素可以根據(jù)相對(duì)關(guān)系自動(dòng)變化以適應(yīng)外部容器變化的布局能力。相對(duì)關(guān)系如占比、固定寬高比、顯示優(yōu)先級(jí)等。當(dāng)前自適應(yīng)布局能力有7種:拉伸能力、均分能力、占比能力、縮放能力、延伸能力、隱藏能力、折行能力。自適應(yīng)布局能力可以實(shí)現(xiàn)界面顯示隨外部容器大小連續(xù)變化。
- [響應(yīng)式布局]:當(dāng)外部容器大小發(fā)生變化時(shí),元素可以根據(jù)斷點(diǎn)、柵格或特定的特征(如屏幕方向、窗口寬高等)自動(dòng)變化以適應(yīng)外部容器變化的布局能力。當(dāng)前響應(yīng)式布局能力有3種:斷點(diǎn)、媒體查詢、柵格布局。
- [GridRow]:柵格容器組件,僅可以和柵格子組件(GridCol)在柵格布局場(chǎng)景中使用。
- [GridCol]:柵格子組件,必須作為柵格容器組件(GridRow)的子組件使用。
環(huán)境搭建
軟件要求
硬件要求
- 開發(fā)板類型:[潤(rùn)和RK3568開發(fā)板]。
- OpenHarmony系統(tǒng):3.2 Release。
環(huán)境搭建
完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進(jìn)行:
- [獲取OpenHarmony系統(tǒng)版本]:標(biāo)準(zhǔn)系統(tǒng)解決方案(二進(jìn)制)。以3.2 Release版本為例:
- 搭建燒錄環(huán)境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發(fā)板的燒錄]
- 搭建開發(fā)環(huán)境。
- 開始前請(qǐng)參考[工具準(zhǔn)備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
- 開發(fā)環(huán)境配置完成后,請(qǐng)參考[使用工程向?qū)創(chuàng)建工程(模板選擇“Empty Ability”)。
- 工程創(chuàng)建完成后,選擇使用[真機(jī)進(jìn)行調(diào)測(cè)]。
- 開發(fā)前請(qǐng)熟悉鴻蒙開發(fā)指導(dǎo)文檔 :
gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
點(diǎn)擊或者復(fù)制轉(zhuǎn)到。
代碼結(jié)構(gòu)解讀
本篇Codelab只對(duì)核心代碼進(jìn)行講解,對(duì)于完整代碼,我們會(huì)在gitee中提供。
“一次開發(fā),多端部署”推薦使用三層目錄的工程結(jié)構(gòu)來(lái)管理工程,上層目錄包括common、features和product,common為公共特性目錄,存放不同形態(tài)設(shè)備公用的類和常量,features為功能模塊目錄,存放應(yīng)用的各個(gè)功能模塊,product為產(chǎn)品層目錄,存放不同形態(tài)設(shè)備范類代碼。本Codelab不涉及功能特性,因此只存在common、product兩個(gè)分層。
├──common // 公共能力層
│ ├──src/main/ets
│ │ ├──constants
│ │ │ └──CommonConstants.ets // 公共常量類
│ │ ├──utils
│ │ │ └──BreakpointSystem.ets // 斷點(diǎn)工具類
│ │ └──viewmodel // 資源類接口
│ │ ├──BottomTabsItem.ets
│ │ ├──DriveTabsItem.ets
│ │ ├──FindTabsItem.ets
│ │ ├──HomeTabsItem.ets
│ │ └──MineTabsItem.ets
│ └──src/main/resources // 資源文件夾
└──product // 產(chǎn)品定制層
├──default/src/main/ets // 支持手機(jī)(含折疊屏)、平板
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口類
│ ├──pages
│ │ └──MainPage.ets // 主頁(yè)面
│ ├──view
│ │ ├──BottomTabsComponent.ets // 底部頁(yè)簽組件
│ │ ├──DriveTabsComponent.ets // 云盤頁(yè)組件
│ │ ├──FindTabsComponent.ets // 發(fā)現(xiàn)頁(yè)組件
│ │ ├──HomeTabsComponent.ets // 首頁(yè)組件
│ │ ├──LeftTabsComponent.ets // 側(cè)邊欄組件
│ │ ├──MineTabsComponent.ets // 個(gè)人頁(yè)組件
│ │ ├──RecentlyPlayedComponent.ets // “最近播放”列表
│ │ └──RecommendComponent.ets // “為你推薦”列表
│ └──viewmodel
│ ├──BottomTabsModel.ets // 底部頁(yè)簽model
│ ├──DriveTabsModel.ets // 云盤頁(yè)model
│ ├──FindTabsModel.ets // 發(fā)現(xiàn)頁(yè)model
│ ├──HomeTabsModel.ets // 首頁(yè)model
│ └──MineTabsModel.ets // 個(gè)人頁(yè)model
└──default/src/main/resources // 資源文件夾
主頁(yè)面框架設(shè)計(jì)
為了操作便捷和充分利用不同形態(tài)設(shè)備的屏幕空間,按屏幕寬度的大小將設(shè)備劃分為3類:
- sm:320vp<=width<520vp,典型設(shè)備為手機(jī)。
- md:520vp<=width<840vp,典型設(shè)備為折疊屏。
- lg:840vp<=width,典型設(shè)備為平板或PC。
根據(jù)用戶使用場(chǎng)景,當(dāng)操作設(shè)備尺寸為sm或md時(shí),一般為豎向使用,此時(shí)用于切換應(yīng)用頁(yè)面的頁(yè)簽欄適合置于底部。當(dāng)操作設(shè)備尺寸為lg時(shí),一般為橫向使用,此時(shí)頁(yè)簽欄適合置于左側(cè)。
// MainPage.ets
@Entry
@Component
struct MainPage {
...
build() {
SideBarContainer(SideBarContainerType.Embed) {
LeftTabs({ bottomTabIndex: $bottomTabIndex }); // 側(cè)邊欄
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.End, justifyContent: FlexAlign.End }) {
Tabs({ barPosition: BarPosition.End, index: 0, controller: this.controller }) {
... // 頁(yè)面內(nèi)容
}
if (this.currentBreakpoint !== Const.LG) {
BottomTabs({ bottomTabIndex: $bottomTabIndex }) // 底部欄,當(dāng)屏幕尺寸不為"lg"時(shí)顯示
}
}
.width(Const.FULL_SIZE)
.backgroundColor($r('app.color.background_color'))
}
.showSideBar(this.currentBreakpoint === Const.LG) // 當(dāng)屏幕尺寸為"lg"時(shí)顯示側(cè)邊欄
.showControlButton(false)
.sideBarWidth(Const.SIDEBAR_WIDTH)
.maxSideBarWidth(Const.SIDEBAR_WIDTH_MAX)
.minSideBarWidth(Const.SIDEBAR_WIDTH_MIN)
}
}
各頁(yè)面代碼實(shí)現(xiàn)
首頁(yè)
首頁(yè)顯示輪播圖和“最近播放”、“為你推薦”兩個(gè)列表,輪播圖根據(jù)屏幕尺寸的區(qū)別,有顯示數(shù)量的不同(sm為1,md為2,lg為3),列表使用具備自適應(yīng)布局能力的List組件。
// HomeTabsComponent.ets
@Component
export struct HomeTabs {
@Link currentBreakpoint: string;
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 設(shè)置sm、md和lg的布局列數(shù)分別為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 } }) {
... // 標(biāo)題
}
.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)
// 根據(jù)屏幕尺寸大小選擇不同的輪播圖數(shù)量
.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)
}
...
}
}
發(fā)現(xiàn)頁(yè)
發(fā)現(xiàn)頁(yè)使用柵格布局實(shí)現(xiàn)“一次開發(fā),多端部署”能力,把sm設(shè)置為4列,md設(shè)置為8列,lg設(shè)置為12列。熱播榜單在不同設(shè)備尺寸上分別占據(jù)4列、6列和8列。
// FindTabsComponent.ets
@Component
export struct FindTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 設(shè)置sm、md和lg的布局列數(shù)分別為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 } }) {
... // 標(biāo)題
}
.height($r('app.float.title_height'))
LazyForEach(new FindDataSource(FindTabsList), (item: FindTabsItem) = > {
// 設(shè)置熱播榜單在sm、md和lg上分別占據(jù)4、6、8列,并且設(shè)置offset屬性保證在不同設(shè)備形態(tài)上都能保持居中
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
}
}) {
... // 榜單內(nèi)容
}
}, (item: FindTabsItem) = > JSON.stringify(item))
}
}
...
}
}
RK3568開發(fā)板上發(fā)現(xiàn)頁(yè)的實(shí)際效果如圖所示:
云盤頁(yè)
云盤頁(yè)的柵格劃分和發(fā)現(xiàn)頁(yè)相同,但是每個(gè)子組件在所有屏幕尺寸上都只占據(jù)2列。
// DriveTabsComponent.ets
@Component
export struct DriveTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 設(shè)置sm、md和lg的布局列數(shù)分別為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 } }) {
... // 標(biāo)題
}
.height($r('app.float.title_height'))
ForEach(DriveList, (item: DriveTabsItem) = > {
// 設(shè)置云盤內(nèi)容在sm、md和lg上均占據(jù)2列
GridCol({ span: { xs: Const.NUM_2, sm: Const.NUM_2, md: Const.NUM_2, lg: Const.NUM_2 } }) {
... // 云盤內(nèi)容
}
}, (item: DriveTabsItem) = > JSON.stringify(item))
}
}
...
}
}
RK3568開發(fā)板上云盤頁(yè)的實(shí)際效果如圖所示:
個(gè)人頁(yè)
個(gè)人頁(yè)的柵格劃分仍然和發(fā)現(xiàn)頁(yè)相同,但子組件在sm、md形態(tài)下占滿全部列,在lg形態(tài)下只占據(jù)8列。
// MineTabsComponent.ets
@Component
export struct MineTabs {
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
GridRow({
// 設(shè)置sm、md和lg的布局列數(shù)分別為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] }
}) {
// 設(shè)置個(gè)人頁(yè)在sm和md上占滿全部列,在lg上占8列,為保證居中在lg上設(shè)置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 }
}) {
... // 個(gè)人頁(yè)內(nèi)容
}
}
.height(Const.FULL_SIZE)
.backgroundColor($r('app.color.mine_background_color'))
}
...
}
}
`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
RK3568開發(fā)板上個(gè)人頁(yè)的實(shí)際效果如圖所示:
-
HarmonyOS
+關(guān)注
關(guān)注
79文章
1967瀏覽量
30017 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3658瀏覽量
16149 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
188瀏覽量
4368
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論