摘要:
宫格导航(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_SM
aboutToAppear() {
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'
break
case 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'
break
case 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)
}
@Builder
buildGirdNavigation(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.router
if (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)
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。