簡介
基于自適應和響應式布局,實現(xiàn)一次開發(fā)、多端部署音樂專輯頁面。
相關概念
- [一次開發(fā),多端部署]:一套代碼工程,一次開發(fā)上架,多端按需部署。支撐開發(fā)者快速高效的開發(fā)支持多種終端設備形態(tài)的應用,實現(xiàn)對不同設備兼容的同時,提供跨設備的流轉、遷移和協(xié)同的分布式體驗。
- [自適應布局]:當外部容器大小發(fā)生變化時,元素可以根據(jù)相對關系自動變化以適應外部容器變化的布局能力。相對關系如占比、固定寬高比、顯示優(yōu)先級等。當前自適應布局能力有7種:拉伸能力、均分能力、占比能力、縮放能力、延伸能力、隱藏能力、折行能力。自適應布局能力可以實現(xiàn)界面顯示隨外部容器大小連續(xù)變化。
- [響應式布局]:當外部容器大小發(fā)生變化時,元素可以根據(jù)斷點、柵格或特定的特征(如屏幕方向、窗口寬高等)自動變化以適應外部容器變化的布局能力。當前響應式布局能力有3種:斷點、媒體查詢、柵格布局。
- [GridRow]:柵格容器組件,僅可以和柵格子組件(GridCol)在柵格布局場景中使用。
- [GridCol]:柵格子組件,必須作為柵格容器組件(GridRow)的子組件使用。
環(huán)境搭建
軟件要求
- [DevEco Studio]版本:DevEco Studio 3.1 Release及以上版本。
- OpenHarmony SDK版本:API version 9及以上版本。
硬件要求
- 開發(fā)板類型:[潤和RK3568開發(fā)板]。
- OpenHarmony系統(tǒng):3.2 Release及以上版本。
環(huán)境搭建
完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進行:
- [獲取OpenHarmony系統(tǒng)版本]:標準系統(tǒng)解決方案(二進制)。以3.2 Release版本為例:
- 搭建燒錄環(huán)境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發(fā)板的燒錄]
- 開發(fā)前請熟悉鴻蒙開發(fā)指導文檔 :
gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
點擊或者復制轉到。
- 搭建開發(fā)環(huán)境。
- 開始前請參考[工具準備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
- 開發(fā)環(huán)境配置完成后,請參考[使用工程向導]創(chuàng)建工程(模板選擇“Empty Ability”)。
- 工程創(chuàng)建完成后,選擇使用[真機進行調(diào)測]。
代碼結構解讀
本篇Codelab只對核心代碼進行講解,對于完整代碼,我們會在gitee中提供。
├──common/src/main/ets // 公共能力層
│ ├──bean
│ │ └──MenuData.ets // 菜單數(shù)據(jù)實體類
│ ├──constants
│ │ ├──BreakpointConstants.ets // 斷點常量類
│ │ ├──GridConstants.ets // 柵格常量類
│ │ └──StyleConstants.ets // 樣式常量類
│ └──utils
│ └──BreakpointSystem.ets // 斷點工具類
├──features // 基礎特性層
│ ├──content/src/main/ets // 專輯封面和歌曲列表內(nèi)容區(qū)
│ │ ├──components
│ │ │ ├──AlbumComponent.ets // 自定義專輯封面組件
│ │ │ ├──AlbumCover.ets // 支持多設備的自定義專輯封面組件
│ │ │ ├──Content.ets // 自定義專輯封面和歌曲列表組件
│ │ │ └──PlayList.ets // 自定義歌曲列表組件
│ │ ├──constants
│ │ │ └──ContentConstants.ets // 內(nèi)容區(qū)常量類
│ │ └──viewmodel
│ │ ├──SongDataSource.ets // 懶加載數(shù)據(jù)源
│ │ └──SongListData.ets // 歌曲列表數(shù)據(jù)類
│ ├──content/src/main/resources // 資源文件目錄
│ ├──header/src/main/ets // 頂部標題欄
│ │ ├──components
│ │ │ └──Header.ets // 自定義標題欄組件
│ │ └──constants
│ │ └──HeaderConstants.ets // 標題欄常量類
│ ├──header/src/main/resources // 資源文件目錄
│ └──player/src/main/ets // 底部播放控制區(qū)
│ │ ├──components
│ │ │ └──Player.ets // 自定義底部播放控制區(qū)組件
│ │ └──constants
│ │ └──PlayerConstants.ets // 播放控制區(qū)常量類
│ └──player/src/main/resources // 資源文件目錄
└──products // 產(chǎn)品定制層
├──phone/src/main/ets // 支持手機、平板
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口類
│ └──pages
│ └──MainPage.ets // 主界面
└──phone/src/main/resources // 資源文件目錄
工程結構管理
在這個章節(jié)中,我們需要完成模塊劃分、梳理模塊間依賴關系并設計代碼結構,從而便于后續(xù)復雜項目的維護。參考上一章節(jié)頁面設計,我們可以將頁面分拆為多個組成部分:
- 標題欄
- 專輯封面
- 歌曲列表
- 播放控制區(qū)
工程結構建議如下:
- 定義common層(公共能力層): 用于存放項目的工具類、公共常量等。需編譯成一個HAR包,只可以被products和features依賴,不可以反向依賴。
- 定義features層(基礎特性層): 用于存放相對獨立的各個功能單元、自定義UI組件或業(yè)務實現(xiàn)邏輯,供產(chǎn)品靈活部署。不需要單獨部署的feature通常編譯為HAR包,供products或其它features使用。需要單獨部署的feature通常編譯為Feature類型的HAP包,和products下Entry類型的HAP包進行組合部署。features層可以橫向調(diào)用及依賴common層,同時可以被products層不同設備形態(tài)的HAP所依賴,但是不能反向依賴products層。
- 定義products層(產(chǎn)品定制層): 用于針對不同設備形態(tài)進行功能和特性集成。products層各個子目錄分別編譯為一個Entry類型的HAP包,作為應用的主入口,products層不可以橫向調(diào)用。
本篇Codelab通過common層管理媒體查詢工具類、柵格常量、公共常量等;按照頁面分組,將標題欄定義為header基礎特性,將專輯封面和歌曲列表定義為content基礎特性,將播放控制區(qū)定義為player基礎特性;定義phone產(chǎn)品目錄支持手機、平板等設備,集成基礎特性層的能力。products依賴features和common,features依賴common但不互相依賴,工程結構清晰且便于維護。
├──common // 公共能力層
├──features // 基礎特性層
│ ├──content // 專輯封面和歌曲列表內(nèi)容區(qū)
│ ├──header // 頂部標題欄
│ └──player // 底部播放控制區(qū)
└──products // 產(chǎn)品定制層
└──phone // 支持手機、平板
一次開發(fā),多端部署實現(xiàn)方案
多標題欄
在這個章節(jié)中,我們需完成頂部標題欄的實現(xiàn)方案。不同設備的標題欄始終只顯示“返回按鈕”、“歌單”以及“更多按鈕”,中間空白區(qū)域通過Blank組件填充,可實現(xiàn)自適應拉伸能力。
// Header.ets
@Component
export struct Header {
@StorageProp('fontSize') fontSize: number = 0;
build() {
Row() {
Image($r('app.media.ic_back'))
.width($r('app.float.icon_width'))
.height($r('app.float.icon_height'))
.margin({ left: $r('app.float.icon_margin') })
Text($r('app.string.play_list'))
.fontSize(this.fontSize + HeaderConstants.TITLE_FONT_SIZE_PLUS)
.fontWeight(HeaderConstants.TITLE_FONT_WEIGHT)
.fontColor($r('app.color.title_color'))
.opacity($r('app.float.title_opacity'))
.letterSpacing(HeaderConstants.LETTER_SPACING)
.padding({ left: $r('app.float.title_padding_left') })
Blank()
Image($r('app.media.ic_more'))
.width($r('app.float.icon_width'))
.height($r('app.float.icon_height'))
.margin({ right: $r('app.float.icon_margin') })
.bindMenu(this.getMenu())
}
.width(StyleConstants.FULL_WIDTH)
.height($r('app.float.title_bar_height'))
.zIndex(HeaderConstants.Z_INDEX)
}
}
專輯封面
在這個章節(jié)中,我們需完成專輯封面的實現(xiàn)方案。專輯封面由圖片、歌單介紹及常用操作(“收藏”“下載”“評論”“分享”)三部分組成,這三部分的布局可以用柵格實現(xiàn)。
- 在sm斷點下,圖片占4個柵格,歌單介紹占8個柵格,常用操作占12個柵格。
- 在md斷點下,圖片、歌單介紹和常用操作分別占12個柵格。
- 在lg斷點下,圖片、歌單介紹和常用操作分別占12個柵格。
// AlbumComponent.ets
@Component
export struct AlbumComponent {
@StorageProp('coverMargin') coverMargin: number = 0;
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';
...
build() {
Column() {
GridRow() {
GridCol({
span: { sm: GridConstants.SPAN_FOUR, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
this.CoverImage()
}
GridCol({
span: { sm: GridConstants.SPAN_EIGHT, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
this.CoverIntroduction()
}
GridCol({
span: { sm: GridConstants.SPAN_TWELVE, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
this.CoverOptions()
}
.padding({
top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? $r('app.float.option_margin') : 0,
bottom: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? $r('app.float.option_margin') : 0
})
}
.padding({
top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ?
$r('app.float.cover_padding_top_sm') : $r('app.float.cover_padding_top_other')
})
}
.margin({
left: this.coverMargin,
right: this.coverMargin
})
}
}
對于“收藏”、“下載”、“評論”、“分享”常用操作,通過設置容器組件的justifyContent屬性為FlexAlign.SpaceEvenly,實現(xiàn)自適應均分能力。對于專輯圖片,設置aspectRatio屬性為1,Image組件寬高比固定為1:1,實現(xiàn)自適應縮放能力。
// AlbumComponent.ets
@Component
export struct AlbumComponent {
...
@Builder
CoverImage() {
Stack({ alignContent: Alignment.BottomStart }) {
...
}
.width(StyleConstants.FULL_WIDTH)
.height(StyleConstants.FULL_HEIGHT)
.aspectRatio(ContentConstants.ASPECT_RATIO_ALBUM_COVER)
}
...
@Builder
CoverOptions() {
Row() {
ForEach(optionList, item = > {
Column({ space: ContentConstants.COVER_OPTION_SPACE }) {
Image(item.image)
.height($r('app.float.option_image_size'))
.width($r('app.float.option_image_size'))
Text(item.text)
.fontColor($r('app.color.album_name_introduction'))
.fontSize(this.fontSize - 1)
}
})
}
.height($r('app.float.option_area_height'))
.width(StyleConstants.FULL_WIDTH)
.justifyContent(FlexAlign.SpaceEvenly)
}
}
歌曲列表
在這個章節(jié)中,我們需完成歌曲列表的實現(xiàn)方案。對于不同屏幕尺寸的設備,歌單列表的樣式基本一致,但sm和md斷點下的歌曲列表是單列顯示,lg斷點下的歌曲列表是雙列顯示。可以通過List組件的lanes屬性實現(xiàn)這一效果。
// PlayList.ets
@Component
export struct PlayList {
@Consume coverHeight: number;
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';
@StorageProp('fontSize') fontSize: number = 0;
...
build() {
Column() {
this.PlayAll()
List() {
LazyForEach(new SongDataSource(songList), (item: SongItem) = > {
ListItem() {
Column() {
this.SongItem(item.title, item.label, item.singer)
Divider()
.strokeWidth(ContentConstants.DIVIDER_STROKE_WIDTH)
.color($r('app.color.black'))
.opacity($r('app.float.divider_opacity'))
}
.padding({
left: $r('app.float.list_item_padding'),
right: $r('app.float.list_item_padding')
})
}
}, (item, index) = > JSON.stringify(item) + index)
}
.width(StyleConstants.FULL_WIDTH)
.height(StyleConstants.FULL_HEIGHT)
.backgroundColor($r('app.color.white'))
.margin({ top: $r('app.float.list_area_margin_top') })
.lanes(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?
ContentConstants.COL_TWO : ContentConstants.COL_ONE)
}
.padding({
top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? 0 : $r('app.float.list_area_padding_top'),
bottom: $r('app.float.list_area_padding_bottom')
})
}
...
}
}
播放控制區(qū)
在這個章節(jié)中,我們需完成底部播放控制區(qū)的實現(xiàn)方案。對于不同屏幕尺寸的設備,播放控制區(qū)顯示的內(nèi)容有差異,sm斷點僅顯示播放按鈕,md斷點顯示上一首、播放和下一首按鈕,lg斷點顯示收藏、上一首、播放、下一首、列表按鈕,可以分別設置按鈕的displayPriority屬性,通過自適應隱藏能力實現(xiàn)。同時,歌曲信息與播放控制按鈕之間通過Blank組件自動填充,實現(xiàn)自適應拉伸能力。
// Player.ets
@Component
export struct Player {
...
build() {
Row() {
...
Blank()
Row() {
Image($r('app.media.ic_favorite'))
...
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_ONE)
Image($r('app.media.ic_previous'))
...
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_TWO)
Image($r('app.media.ic_pause'))
...
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_THREE)
Image($r('app.media.ic_next'))
...
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_TWO)
Image($r('app.media.ic_music_list'))
...
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_ONE)
}
.layoutWeight(PlayerConstants.LAYOUT_WEIGHT_PLAYER_CONTROL)
.justifyContent(FlexAlign.End)
}
...
}
}
特性集成
在這個章節(jié)中,我們將標題欄(header基礎特性)、專輯內(nèi)容(content基礎特性)、播放控制區(qū)(player基礎特性)集成到一起,作為應用的主入口。同時,后續(xù)項目如果新增更多的產(chǎn)品例如穿戴設備、智慧屏等,可定義新的products產(chǎn)品、自由集成不同的features基礎特性,從而達成代碼盡量復用、產(chǎn)品自由定制的目標。
// MainPage.ets
@Component
struct MainPage {
...
build() {
Stack({ alignContent: Alignment.Top }) {
Header()
Content()
Player()
}
...
}
`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
解決系統(tǒng)能力差異的兼容性問題
前面章節(jié)主要介紹了如何解決頁面適配、工程管理的問題,本章節(jié)主要介紹應用如何解決設備系統(tǒng)能力差異的兼容問題。
我們以一個簡單例子說明:一個具有音頻設備管理功能的應用,如何兼容地運行在有音頻設備管理和無音頻設備管理系統(tǒng)能力的設備上。
從開發(fā)到用戶安裝、運行,一般要經(jīng)歷幾個階段:應用分發(fā)和下載->應用安裝->應用運行,要解決上面提到的兼容問題,一般有如下幾種解決思路:
- 分發(fā)和下載:有音頻設備管理設備的用戶才能在應用市場可見該應用,供用戶下載。
- 安裝:有音頻設備管理能力的設備才允許安裝該應用。
- 運行:在運行階段通過動態(tài)判斷方式,在有音頻設備管理設備上功能運行正常,在無音頻設備管理設備上運行不發(fā)生Crash。
所以,對應的解決方案就有:
- 在分發(fā)階段,上架的應用使用的系統(tǒng)能力是設備系統(tǒng)能力的子集。滿足這個條件,用戶才能在應用市場看到該應用。
- 在安裝階段,安裝的應用調(diào)用的系統(tǒng)能力是設備系統(tǒng)能力的子集。滿足這個條件,用戶才能安裝該應用。
- 通過canIUse接口判斷是否支持某個系統(tǒng)能力。
本篇Codelab以音頻設備管理為例,僅介紹canIUse接口判斷的方式。點擊右上角更多,如果當前系統(tǒng)支持音頻設備管理能力,則顯示“音頻設備管理”菜單選項,否則不顯示。
// Header.ets
@Component
export struct Header {
...
getMenu(): MenuData[] {
let menuItem: MenuData = new MenuData();
let menuDatas: MenuData[] = [];
if (canIUse(HeaderConstants.SYSCAP_ETHERNET)) {
menuItem.value = HeaderConstants.AUDIO_DEVICE_SERVICE;
menuItem.action = () = > {
promptAction.showToast({
message: HeaderConstants.AUDIO_DEVICE_SERVICE,
duration: HeaderConstants.TOAST_DURATION
});
};
menuDatas.push(menuItem);
}
return menuDatas;
}
}
-
HarmonyOS
+關注
關注
79文章
1975瀏覽量
30194 -
OpenHarmony
+關注
關注
25文章
3722瀏覽量
16317 -
鴻蒙OS
+關注
關注
0文章
188瀏覽量
4391
發(fā)布評論請先 登錄
相關推薦
評論