头图
图片导航组件(A_ImageNav):可设置图片数据源(含文本、图片地址及路由)、按钮颜色、背景图高度、背景图模式/图片模式、非背景图模式时可设置导航标题和导航子标题。



一、组件调用方式

1.背景图模式调用方法:

首先输入 A_ImageNav,然后给 data 和 color 属性赋值,如下图所示:

在属性 data 中需要给按钮的文字(text)、背景图(src)、跳转页面的路由(router)、标题(title)和子标题(subTitle)赋值,如下图所示:

在本地模拟器上运行效果如下:

2.非背景图模式调用方法:

需要给导航数据 data 赋值,同时将bgImageMode设置为false(非背景图模式)。根据需要,可以给导航标题(navTitle)和导航子标题(navSubTitle)赋值,如下图所示:

在属性 data 中仅需要给文本(text)、小图标的图片(src)、跳转页面的路由(router)赋值,如下图所示:

在本地模拟器上浅色模式和深色模式下,运行效果如下:

二、在线排版

点击“App引导页”的“设计页面”按钮,进入页面设计器:

从组件库将“图片导航组件”拖拽到页面设计器排版区,如下图所示:

在右侧组件设置面板中,可以设置按钮的背景颜色。也可以根据图片的实际尺寸调整背景图的高度,修改后记得点击“保存设置”按钮。如下图所示:

点击“配置数据”按钮,为每个导航项绑定真实存在的页面作为路由跳转的目标,也可以根据自己的业务实际需要,修改按钮文字、背景图、标题和子标题。如下图所示:

侧栏菜单中,选择“纯血鸿蒙”,然后点击页面右上角的“代码魔法”图标,一秒生成生产级纯血鸿蒙项目工程,然后选择“引导页”(GuideView.ets),查看根据配置生成的鸿蒙代码。如下图所示:

现在我们继续进入页面设计器,将组件的模式改为“非背景图模式”,在组件设置面板中,将“背景图模式”属性设置为“否”,如下图所示:

可根据业务需要增加导航标题和导航子标题,如下图所示:

在“非背景图模式”下,配置数据中的标题和子标题不是必要的,可以清空,如下图所示:

再次生成鸿蒙代码:

三、源码解析

在“图片导航组件”当中有6个属性:

data 是一个数组(Array)类型的数据,它接受 ImageModel 的数据结构,包含图片地址、按钮、文字、大标题、子标题和页面跳转的路由,其中大标题和子标题仅在背景图模式时需要赋值,如下图所示:

ImageModel源码如下:

/*
 * 图片导航Model
 * src:图片地址(支持"https://xxx"远程图片和media文件夹的本地图片)
 * text:文字
 * title:标题
 * subTitle:子标题
 * router:路由
 */

interface ImageModel {
  src: string
  text?: string
  title?: string
  subTitle?: string
  router?: string
};

export { ImageModel };

现在,我们继续分析图片导航组件 A_ImageNav 的源码。

首先以手机设备为标准初始化相关内部状态变量。@State注解在这里无须了解,后续有课程系统讲解。这里只需要关心如下变量:compImageSize是非背景图模式时小图标图片的尺寸,compImageMarginTop和compImageMarginBottom是该图片的上下外边距,compWidth和compHeight是该图片所在卡片容器的尺寸。这几个变量仅在非背景图模式时有作用。如下图所示:

aboutToAppear函数在创建自定义组件的新实例后,在执行其build()函数之前执行。 在aboutToAppear函数中,针对设备类型是折叠屏或者平板时的变量值做了适配,实现“一端开发,多端部署”(简称“一多”)的处理,同时,对于图片不是远程图片时做了本地资源的路径处理,如下图所示:

在背景图模式时,通过 ForEach 循环遍历轮播图数据,下面有一个列组件(Column),利用多个属性控制背景图的表现,其中width控制背景图宽度,手机设备时为100%(常量值:GirdConstants.FULL_PERCENT),折叠屏和平板时宽度为设备屏幕宽度的70%;height属性控制背景图的高度,利用backgroundImageHeight赋值;背景图片(backgroundImage)支持远程图片或本地资源;borderRadius控制圆角,padding控制内间距,如下图所示:

展开列组件,用两个文本组件(Text)展示大标题和子标题,用按钮组件(Button)展示按钮,按钮的背景颜色由组件属性 color 决定,默认是info(蓝色)。如下图所示:

按钮的点击事件(onClick)实现页面路由跳转,如下图所示:

现在,将背景图模式的相关代码折叠隐藏起来。非背景图模式的时候,使用中号标题组件(A_Title_M)展示导航标题的文本,使用大号子标题组件(A_SubTitle_L)展示导航子标题的文本,如下图所示:

在List组件中,使用 ForEach 遍历导航数据,每项数据通过列组件(Column)包裹起来,设置宽、高,圆角、背景颜色和点击后路由跳转的事件。如下图所示:

在列组件内部,有两个元素,第一个是图片(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_ImageNav({
   data: [
     {
       text: '立即体验',
       src: 'https://cdn.aigcoder.com/sample/ImageNav/pic1.jpeg',
       router: 'MainTabView',
       title: 'AI极客',
       subTitle: 'AIGC赋能程序开发,降本增效,快速生成前后端全技术栈代码。'
     },
     {
       text: '设计App',
       src: 'https://cdn.aigcoder.com/sample/ImageNav/pic2.png',
       router: 'OrderDetail/2',
       title: '纯血鸿蒙',
       subTitle: '纯血鸿蒙开发神器,快速开发高质量鸿蒙应用。'
     }
   ],
   color: ColorConstants.DANGER
 })

 调用示例二:
 A_ImageNav({
   data: [
     {
       text: 'AIGC',
       src: 'https://cdn.aigcoder.com/sample/ImageNav/pic3.png',
       router: 'MainTabView'
     },
     {
       text: '纯血鸿蒙',
       src: 'https://cdn.aigcoder.com/sample/ImageNav/pic4.png',
       router: 'OrderList'
     }
   ],
   bgImageMode: false,
   navTitle: 'AIGC低代码平台',
   navSubTitle: 'AIGC,纯血鸿蒙',
 })
 */

import { ColorConstants } from "../../constants/ColorConstants"
import { FloatConstants } from "../../constants/FloatConstants"
import { GirdConstants } from "../../constants/GirdConstants"
import { ImageModel } from "../../models/ImageModel"
import { A_Title_M } from "../text/A_Title_M"
import { A_SubTitle_L } from "../text/A_SubTitle_L"

/**
 * 【图片导航】
 * data:图片数据
 * color:按钮颜色
 * backgroundImageHeight:背景图高度
 * bgImageMode:背景图模式(默认)
 * navTitle:导航标题(非背景图模式时可选)
 * navSubTitle:导航子标题(非背景图模式时可选)
 */
@Component
export struct A_ImageNav {
  @Prop data: Array<ImageModel>
  @Prop color?: ResourceStr
  @Prop backgroundImageHeight?: string = '200vp'
  @Prop bgImageMode?: boolean = true
  @Prop navTitle?: string = ''
  @Prop navSubTitle?: string = ''

  @StorageLink('pageInfo') pageInfo: NavPathStack = new NavPathStack()
  @StorageLink('deviceType') deviceType: string = GirdConstants.DEVICE_SM

  @State compImageSize: Length = '62vp'
  @State compImageMarginTop: Length = '28vp'
  @State compImageMarginBottom: Length = '12vp'
  @State compWidth: Length = '264vp'
  @State compHeight: Length = '148vp'
  @State compTextMarginTop: Length = '150vp'
  private compData: Array<ImageModel> = this.data

  aboutToAppear(): void {
    this.compData.forEach(element => {
      switch (this.deviceType){
        case GirdConstants.DEVICE_MD:
        case GirdConstants.DEVICE_LG:
          this.compImageMarginTop = '70vp'
          this.compImageMarginBottom = '24vp'
          this.compWidth = '168vp'
          this.compHeight = '248vp'
          break
        case GirdConstants.DEVICE_SM:
        default:
          break
      }
      if(!(element.src.startsWith("http://") || element.src.startsWith("https://"))){
        element.src = 'app.media.' + element.src
      }
    })
  }

  build() {
    Column({space:GirdConstants.TWELVE}) {
      if(this.bgImageMode){
        ForEach(this.compData, (item: ImageModel) => {
          Column() {
            Text(item.title)
              .fontSize(FloatConstants.FONT_SIZE_TITLE_M)
              .fontColor(ColorConstants.FG_LEVEL1_ALPHA)
            Text(item.subTitle)
              .fontSize(FloatConstants.FONT_SIZE_SUB_TITLE_L)
              .fontColor(ColorConstants.FG_LEVEL2_ALPHA)
              .margin({
                top: FloatConstants.SPACE_S
              })
            Blank()
            Column() {
              Button() {
                Text(item.text)
                  .fontSize(FloatConstants.FONT_SIZE_BODY_L)
                  .fontColor(ColorConstants.FG_LEVEL1_ALPHA)
              }
              .backgroundColor(this.color)
              .width(FloatConstants.BUTTON_WIDTH1)
              .height(FloatConstants.HEIGHT_BUTTON_L)
              .borderRadius(FloatConstants.RADIUS_BUTTON_L)
              .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)
                  }
                }
              })
            }
            .alignItems(HorizontalAlign.End)
            .width(GirdConstants.FULL_PERCENT)
          }
          .width(this.deviceType === GirdConstants.DEVICE_SM ? GirdConstants.FULL_PERCENT : GirdConstants.SEVENTY_PERCENT)
          .height(this.backgroundImageHeight)
          .margin({ top: FloatConstants.SPACE_L })
          .backgroundImage((item.src.startsWith("http://") || item.src.startsWith("https://")) ? item.src : $r(item.src))
          .backgroundImageSize({
            width: GirdConstants.FULL_PERCENT,
            height: this.backgroundImageHeight
          })
          .borderRadius(FloatConstants.RADIUS_XL)
          .padding(FloatConstants.PADDING_CARD)
          .alignItems(HorizontalAlign.Start)
          .justifyContent(FlexAlign.SpaceBetween)
        }, (item: ImageModel, index?: number) => index + JSON.stringify(item))
      } else {
        A_Title_M({text:this.navTitle})
          .margin({
            top: this.deviceType === GirdConstants.DEVICE_SM ? GirdConstants.ZERO : this.compTextMarginTop
          })

        A_SubTitle_L({text:this.navSubTitle})

        List() {
          ForEach(this.compData, (item: ImageModel, index: number) => {
            ListItem() {
              Column({space:GirdConstants.TWELVE}) {
                Image((item.src.startsWith("http://") || item.src.startsWith("https://")) ? item.src : $r(item.src))
                  .width(this.compImageSize)
                  .height(this.compImageSize)
                  .borderRadius(FloatConstants.RADIUS_S)
                  .margin({
                    top: this.compImageMarginTop,
                    bottom: this.compImageMarginBottom
                  })

                Text(item.text)
                  .fontColor(ColorConstants.FG_LEVEL3)
                  .fontSize(FloatConstants.FONT_SIZE_BODY_L)
                  .lineHeight(FloatConstants.SPACE_TEXT_LINE_L)
                  .fontWeight(FontWeight.Medium)
                  .margin({
                    bottom: this.compImageMarginTop
                  })
              }
              .width(this.compWidth)
              .height(this.compHeight)
              .borderRadius(FloatConstants.RADIUS_L)
              .backgroundColor(ColorConstants.CARD_BG)
              .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(this.deviceType === GirdConstants.DEVICE_SM ? GirdConstants.FULL_PERCENT : this.compWidth)
            .height(this.compHeight)
            .margin({
              top: FloatConstants.SPACE_L,
              right: this.deviceType === GirdConstants.DEVICE_SM ? GirdConstants.ZERO : FloatConstants.PADDING_CARD
            })
          }, (item: ImageModel) => JSON.stringify(item))
        }
        .padding({
          left: this.deviceType === GirdConstants.DEVICE_SM ? GirdConstants.ZERO : FloatConstants.PADDING_CARD
        })
        .listDirection(this.deviceType === GirdConstants.DEVICE_SM ? Axis.Vertical : Axis.Horizontal)
      }
    }
  }
}

华哥的全栈次元舱
1 声望0 粉丝