摘要:
宮格導航(A_GirdNav):可設置導航數據,建議導航項超過16個,可設置“更多”圖標指向的頁面路由。最多顯示兩行,手機每行最多顯示4個圖標,折疊屏每行最多6個圖標,平板每行最多8個圖標。多余圖標通過“更多”圖標跳轉。
一、組件調用方式
宮格導航組件的調用非常簡單,只需要輸入A_GirdNav ,然后給屬性 data(導航數據)和 moreRouter(“更多”圖標指向的頁面路由)賦值即可。在 data 當中需要給文字、圖標和跳轉的路由賦值。如下圖所示:
頁面調用代碼如下:
import { A_TitleBar } from '../aui/basic/A_TitleBar'
import { A_GirdNav } from '../aui/navigation/A_GirdNav'
import { ColorConstants } from '../constants/ColorConstants'
import { FloatConstants } from '../constants/FloatConstants'
import { GirdConstants } from '../constants/GirdConstants'
import { WindowUtils } from '../utils/WindowUtils'@Component
export struct Sample2 {@StorageLink('pageInfo') pageInfo: NavPathStack = new NavPathStack()@StorageLink('primaryColor') primaryColor: ResourceStr = ColorConstants.INFO@StorageLink('deviceType') deviceType: string = GirdConstants.DEVICE_SMaboutToAppear() {WindowUtils.getInstance()?.setFullScreen()}build() {Row() {Column() {// 標題欄A_TitleBar({ text: '調用示例二' })// 下拉容器Scroll() {GridRow({columns: { sm: GirdConstants.GRID_COLUMNS[0], md: GirdConstants.GRID_COLUMNS[1], lg: GirdConstants.GRID_COLUMNS[2] },gutter: { x: GirdConstants.GRID_GUTTER, y: GirdConstants.GRID_GUTTER },breakpoints: { reference: BreakpointsReference.WindowSize } }) {GridCol({ span: 12 }) {A_GirdNav({data: [{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' }],moreRouter: 'MoreNavigation'})}.margin({top: FloatConstants.SPACE_TOP})}.padding({ bottom:this.deviceType === GirdConstants.DEVICE_LG ? GirdConstants.ZERO : FloatConstants.SPACE_S })}.scrollBar(BarState.Off)}.height(GirdConstants.FULL_PERCENT)}.alignItems(VerticalAlign.Top).padding({top: FloatConstants.SPACE_TOP,left:FloatConstants.SPACE_LEFT,right:FloatConstants.SPACE_RIGHT,bottom:this.deviceType === GirdConstants.DEVICE_LG ? GirdConstants.GRID_BOTTOM : FloatConstants.SPACE_BOTTOM}).width(GirdConstants.FULL_PERCENT).backgroundColor(ColorConstants.APP_BG)}
}
本地模擬器下淺色模式和深色模式運行效果如下:
二、在線排版
在會員頁面或管理后臺的頁面當中選擇一個頁面,比如“首頁”,然后點擊“頁面設計”。如下圖所示:
現在“首頁”是一個空頁面,還沒有任何組件。展開左側組件庫中的“導航組件”分組,將“宮格導航組件”拖拽到頁面中,如下圖所示:
選擇右側“組件設置”下的“宮格導航”組件,點擊“配置數據”按鈕,可以根據自己的業務需要,新增、刪除或修改每一項配置,如下圖所示:
現在,切換到代碼魔法頁面,我們可以在側欄菜單這里選擇“純血鴻蒙”,(可以切換為深色模式,看代碼更舒適),然后點擊“生成代碼”,如下圖所示:
在生成的鴻蒙項目中,展開特性層(features),選擇 home 模塊,找到 /src/main/ets/view 目錄下的 HomeView.ets 頁面,查看生成的代碼,如下圖所示:
三、源碼解析
宮格導航組件支持兩個屬性,一個是 data(導航數據,建議導航項超過16個),另外一個是 moreRouter(“更多”圖標指向的頁面路由):
卡片導航數據 data 是一個數組 Array<NavigationModel>,其中導航Model的數據結構如下,含三個字段:導航的文字(text)、導航圖標(icon)和跳轉頁面的路由(router)。
繼續分析宮格導航組件 A_GirdNav 的源碼,在 aboutToAppear() 這個生命周期當中,對設備為折疊屏、平板和手機時的導航數據做了差異化的處理,以實現“一多”適配。其中,設備為折疊屏時,一行最多顯示6個圖標,最多顯示兩行。當總的圖標數(每個圖標對應一項導航數據)小于等于6個時,將所有圖標放入一組數據;當總圖標數大于6個時分為兩組數據,第一組數據取前六項導航數據,第二組數據分兩種情況:當總圖標數大于12個時(超過兩行),則將第7-11個圖標放入第二組數據,然后加上一個“更多”圖標;如果總圖標數不超過12個圖標,則將第7個到最后的圖標放入第二組數據。如下圖所示:
設備為平板時,一行最多顯示8個圖標,最多顯示兩行。當總的圖標數小于等于8個時,將所有圖標放入一組數據;當總圖標數大于8個時分為兩組數據,第一組數據取前八項導航數據,第二組數據分兩種情況:當總圖標數大于16個時(超過兩行),則將第9-15個圖標放入第二組數據,然后加上一個“更多”圖標;如果總圖標數不超過16個圖標,則將第9個到最后的圖標放入第二組數據。如下圖所示:
設備為手機時,一行最多顯示4個圖標,最多顯示兩行。當總的圖標數小于等于4個時,將所有圖標放入一組數據;當總圖標數大于4個時分為兩組數據,第一組數據取前四項導航數據,第二組數據分兩種情況:當總圖標數大于8個時(超過兩行),則將第5-7個圖標放入第二組數據,然后加上一個“更多”圖標;如果總圖標數不超過8個圖標,則將第5個到最后的圖標放入第二組數據。如下圖所示:
最后,將原始導航數據中的圖標數據做了預處理,變為符合鴻蒙資源命名規則的方式,如下代碼所示:
this.compData.forEach(element => {element.icon = 'app.media.' + (element.icon.startsWith('mdi-') || element.icon.startsWith('mdi_') ? '' : 'mdi_') + element.icon.replaceAll('-', '_')})this.compData2.forEach(element => {element.icon = 'app.media.' + (element.icon.startsWith('mdi-') || element.icon.startsWith('mdi_') ? '' : 'mdi_') + element.icon.replaceAll('-', '_')})
使用自定義構建函數buildGirdNavigation展示兩組導航數據,如下圖所示:
ArkUI提供了一種輕量的UI元素復用機制@Builder,其內部UI結構固定,僅與使用方進行數據傳遞,開發者可以將重復使用的UI元素抽象成一個方法,在build方法里調用。為了簡化語言,我們將@Builder裝飾的函數也稱為“自定義構建函數”。使用@Builder裝飾自定義構建函數buildGirdNavigation,通過ForEach循環每一組導航數據,內部通過Image組件包裝圖標,Text組件包裝文字,使用點擊事件實現頁面路由跳轉,如下圖所示:
宮格導航組件的完整源碼如下:
/** Copyright (c) 2024 AIGCoder.com(AI極客)* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*//**調用示例一:A_GirdNav({data: [{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' }]})調用示例二:“更多”圖標指向頁面路由A_GirdNav({data: [{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' },{ text: 'Java', icon: 'mdi-language-java', router: 'OrderDetail/2' },{ text: 'Python', icon: 'mdi-language-python', router: '' },{ text: 'Vuetify', icon: 'cast-variant', router: '' },{ text: '純血鴻蒙', icon: 'mdi-tablet-cellphone', router: 'AccountPage' }],moreRouter: 'MoreNavigation'})*/import { ColorConstants } from "../../constants/ColorConstants"
import { FloatConstants } from "../../constants/FloatConstants"
import { GirdConstants } from "../../constants/GirdConstants"
import { NavigationModel } from "../../models/NavigationModel"/*** 【宮格導航】* data:導航數據(建議導航項超過16個)* moreRouter:“更多”圖標指向的頁面路由*/
@Component
export struct A_GirdNav {@Prop data: Array<NavigationModel>@Prop moreRouter?: string@StorageLink('pageInfo') pageInfo: NavPathStack = new NavPathStack()@StorageLink('deviceType') deviceType: string = GirdConstants.DEVICE_SM@State compMore: NavigationModel = {text: '更多',icon: 'dots-horizontal-circle-outline',router: this.moreRouter}@State compBlank: NavigationModel = {text: '',icon: '',router: ''}@State compPadding: Length = '16vp'private compMarginTop: Length = '36vp'private compMarginBottom: Length = '12vp'private compCardHeight: Length = '136vp'private compData: Array<NavigationModel> = []private compData2: Array<NavigationModel> = []aboutToAppear(): void {switch (this.deviceType){case GirdConstants.DEVICE_MD:if(this.data.length > 6){this.compData = this.data.slice(0, 6)if(this.data.length > 12){this.compData2 = this.data.slice(6, 11)this.compData2.push(this.compMore)}else{this.compData2 = this.data.slice(6, this.data.length)for (let i = 0; i < 12-this.data.length; i++) {this.compData2.push(this.compBlank)}}}else{this.compData = this.data.slice(0, this.data.length)}this.compCardHeight = this.compData2.length > 0 ? '144vp' : '80vp'breakcase GirdConstants.DEVICE_LG:if(this.data.length > 8){this.compData = this.data.slice(0, 8)if(this.data.length > 16){this.compData2 = this.data.slice(8, 15)this.compData2.push(this.compMore)}else{this.compData2 = this.data.slice(8, this.data.length)for (let i = 0; i < 16-this.data.length; i++) {this.compData2.push(this.compBlank)}}}else{this.compData = this.data.slice(0, this.data.length)}this.compPadding = '24vp'this.compMarginBottom = '16vp'this.compCardHeight = this.compData2.length > 0 ? '160vp' : '96vp'breakcase GirdConstants.DEVICE_SM:default:if(this.data.length > 4){this.compData = this.data.slice(0, 4)if(this.data.length > 8){this.compData2 = this.data.slice(4, 7)this.compData2.push(this.compMore)}else{this.compData2 = this.data.slice(4, this.data.length)for (let i = 0; i < 8-this.data.length; i++) {this.compData2.push(this.compBlank)}}}else{this.compData = this.data.slice(0, this.data.length)}this.compCardHeight = this.compData2.length > 0 ? '136vp' : '80vp'break}this.compData.forEach(element => {element.icon = 'app.media.' + (element.icon.startsWith('mdi-') || element.icon.startsWith('mdi_') ? '' : 'mdi_') + element.icon.replaceAll('-', '_')})this.compData2.forEach(element => {element.icon = 'app.media.' + (element.icon.startsWith('mdi-') || element.icon.startsWith('mdi_') ? '' : 'mdi_') + element.icon.replaceAll('-', '_')})}build() {Column() {this.buildGirdNavigation(this.compData)this.buildGirdNavigation(this.compData2)}.backgroundColor(ColorConstants.CARD_BG).width(GirdConstants.FULL_PERCENT).justifyContent(FlexAlign.SpaceBetween).borderRadius(FloatConstants.RADIUS_CARD).padding(this.compPadding).margin({top: this.compMarginTop,bottom: this.compMarginBottom}).height(this.compCardHeight)}@BuilderbuildGirdNavigation(list: NavigationModel[]) {Row() {ForEach(list, (item: NavigationModel) => {Column({ space: GirdConstants.EIGHT }) {Image($r(item.icon)).width(FloatConstants.IMAGE_SIZE5).height(FloatConstants.IMAGE_SIZE5).fillColor(ColorConstants.FG_LEVEL1)Text(item.text).fontSize(FloatConstants.FONT_SIZE_BODY_S).fontColor(ColorConstants.FG_LEVEL1)}.onClick(() => {if (item.router) {// 跳轉到新頁面const router = item.routerif (router.includes("/")) {this.pageInfo.pushPathByName(router.substring(0, router.indexOf("/")),router.substring(router.indexOf("/") + 1))} else {this.pageInfo.pushPathByName(router, null)}}}).width(FloatConstants.IMAGE_SIZE10)}, (item: NavigationModel, index: number) => index + JSON.stringify(item))}.justifyContent(FlexAlign.SpaceBetween).width(GirdConstants.FULL_PERCENT)}
}