【HarmonyOS应用开发——ArkTS语言】购物商城的实现【合集】

目录

😋环境配置:华为HarmonyOS开发者

📺演示效果:

📖实验步骤及方法:

1. 在src/main/ets文件中创建components文件夹并在其中创建Home.ets和HomeProduct.ets文件。​

2. 在Home.ets文件中定义 Home 组件,进行商城主页的布局与相关功能的部署。

导入模块

Home 组件定义

aboutToAppear 方法

build 方法(核心界面构建逻辑)

(1)整体布局结构:

(2)搜索栏部分:

(3)主体内容部分(基于 List 组件)

自定义文本样式扩展函数

3. 在HomePruduct.ets文件中定义 HomeProduct 组件,展示商城主页中的内容。

接口定义

HomeProduct 组件定义

组件状态定义

aboutToAppear 生命周期方法

getItem 构建函数

build 方法(核心界面构建逻辑)

4. 在src/main/ets/pages/Index.ets文件中,定义Index 组件作为应用的入口组件。

导入模块

Index 组件定义与入口标识

aboutToAppear 生命周期方法

组件状态定义

myBuilder 构建函数

build 方法(核心界面构建逻辑)

👋实验小结


😋环境配置:华为HarmonyOS开发者

🎯学习小目标:实现如下的购物商城主页效果

📺演示效果:

📖实验步骤及方法:

1. 在src/main/ets文件中创建components文件夹并在其中创建Home.ets和HomeProduct.ets文件。

2. 在Home.ets文件中定义 Home 组件,进行商城主页的布局与相关功能的部署。

实现代码如下:

import font from '@ohos.font'
import HomeProduct from '../components/HomeProduct'
@Component
export default struct Home{
  aboutToAppear(): void {
    font.registerFont({
      familyName: 'myFont',
      familySrc: '/fonts/iconfont.ttf'
    })
  }
  build(){
    Column(){
      Row(){
        Image($r('app.media.logoCircle'))
          .width(40)
        Row(){
          TextInput({placeholder:"搜索内容"})
            .layoutWeight(1)
            .fontSize(16)
            .backgroundColor(Color.Transparent)
          Text("\ue679")
            .width(40)
            .height('100%')
            .backgroundColor('#fa2a83')
            .fontFamily('myFont')
            .fontSize(20)
            .fontColor('#fff')
            .fontWeight('bolder')
            .borderRadius({topLeft:0,topRight:20,bottomLeft:0,bottomRight:20})
            .textAlign(TextAlign.Center)
        }
        .height(35)
        .padding({left:5})
        .backgroundColor('#fff')
        .layoutWeight(1)
        .margin({left:3})
        .borderRadius(20)
      }
      .width('100%')
      .padding({top:10,left:'10%',right:"10%",bottom:10})
      .backgroundColor('#0966b4')
      //主体内容
      List(){
        ListItem(){
          Swiper(){
            Image($r('app.media.img01'))
            Image($r('app.media.img02'))
            Image($r('app.media.img03'))
            Image($r('app.media.img04'))
            Image($r('app.media.img05'))
            Image($r('app.media.img06'))
          }
          .width('100%')
          .aspectRatio(2)
          .loop(true)
          .autoPlay(true)
          .interval(3000)
          .indicator(
            Indicator.dot()
              .itemWidth(10)
              .itemHeight(10)
              .selectedItemWidth(20)
              .selectedItemHeight(10)
              .color(Color.White)
              .selectedColor(Color.Red)
          )
        }
        ListItem(){
          Grid(){
            GridItem(){
              Column(){
                Text('\ue67d')
                  .listItem()
                Text('店铺')
                  .icoText()
              }
            }
            GridItem(){
              Column(){
                Text('\ue632')
                  .listItem()
                Text('陶瓷')
                  .icoText()
              }
            }
            GridItem(){
              Column(){
                Text('\ue61f')
                  .listItem()
                Text('二手书')
                  .icoText()
              }
            }
            GridItem(){
              Column(){
                Text('\ue652')
                  .listItem()
                Text('服务')
                  .icoText()
              }
            }
          }
        .width('100%')
        .height('100%')
          .rowsTemplate('1fr')
          .columnsTemplate('1fr 1fr 1fr 1fr')
        }
        .width('100%')
        .height(100)
        .margin({top:5,bottom:5})
        //推荐标题
        ListItem(){
          Row(){
            Text('推荐好物')
              .fontSize('100%')
              .height(30)
              .fontWeight('bolder')
              .fontColor('#0966b4')
            Text('更多⇨')
              .fontSize(12)
              .fontColor('#0966b4')
          }
          .backgroundColor('#d1d1d1')
          .justifyContent(FlexAlign.SpaceBetween)
          .width('100%')
          .padding(10)
        }
        ListItem(){
          HomeProduct()
        }
      }
      .layoutWeight(1)
      .backgroundColor(Color.White)
    }
  }
}
@Extend(Text)function listItem(){
  .width(60)
  .height(60)
  .backgroundColor('#0966b4')
  .fontFamily('myFont')
  .fontSize(35)
  .fontColor('#fff')
  .borderRadius(30)
  .textAlign(TextAlign.Center)
}
@Extend(Text)function icoText(){
  .fontSize(15)
  .height(30)
  .fontWeight('bolder')
}

该组件构建了一个具有特定布局和功能的界面,包含了搜索栏、轮播图、网格布局展示分类以及推荐好物相关展示等内容。
 

  • 导入模块
import font from '@ohos.font';
import HomeProduct from '../components/HomeProduct';

        从 @ohos.font 导入了 font 模块,可能用于字体相关的操作,比如注册自定义字体等,从后续代码中对字体注册的使用可以印证这一点。

        导入了自定义的 HomeProduct 组件,推测这个组件用于展示具体的推荐好物等相关内容,不过代码中未给出 HomeProduct 的具体实现细节。

  • Home 组件定义
import font from '@ohos.font';
import HomeProduct from '../components/HomeProduct';
        使用 @Component 装饰器将 Home 结构体标记为一个组件,意味着它可以在界面构建中被当作一个独立的 UI 单元来使用,并且按照其内部定义的 build 方法来渲染具体的界面内容。
 
  • aboutToAppear 方法
aboutToAppear(): void {
  font.registerFont({
    familyName: 'myFont',
    familySrc: '/fonts/iconfont.ttf'
  })
}

        这是一个生命周期相关的方法,在组件即将显示时被调用(根据名称和常见的组件生命周期逻辑推测)。

        它调用了 font 模块的 registerFont 方法,目的是注册一个名为 myFont 的自定义字体,字体文件来源指定为 /fonts/iconfont.ttf,这样后续就可以在组件中使用这个自定义字体来显示特定的文本样式了。
 

  • build 方法(核心界面构建逻辑)
(1)整体布局结构:

        整个界面构建基于 Column(列布局),在这个列布局内部嵌套了多个 Row(行布局)以及其他复杂的组件,来构建出最终的页面结构。

(2)搜索栏部分:
Row(){
  Image($r('app.media.logoCircle'))
   .width(40)
  Row(){
    TextInput({placeholder:"搜索内容"})
     .layoutWeight(1)
     .fontSize(16)
     .backgroundColor(Color.Transparent)
    Text("\ue679")
     .width(40)
     .height('100%')
     .backgroundColor('#fa2a83')
     .fontFamily('myFont')
     .fontSize(20)
     .fontColor('#fff')
     .fontWeight('bolder')
     .borderRadius({topLeft:0,topRight:20,bottomLeft:0,bottomRight:20})
     .textAlign(TextAlign.Center)
  }
 .height(35)
 .padding({left:5})
 .backgroundColor('#fff')
 .layoutWeight(1)
 .margin({left:3})
 .borderRadius(20)
}
.width('100%')
.padding({top:10,left:'10%',right:"10%",bottom:10})
.backgroundColor('#0966b4')
  • 外层 Row 作为整体搜索栏的容器,设置了一定的内边距、背景颜色、外边距等样式,并且宽度占满父容器(width('100%'))。
  • 内部首先有一个 Image 组件,显示一个宽度为 40 的图片(可能是应用的 logo 之类的元素),其资源通过 $r('app.media.logoCircle') 方式引用(具体的资源加载机制依赖于框架实现)。
  • 接着又是一个 Row,里面包含 TextInput 和 Text 组件,构建了一个常见的搜索输入框搭配搜索图标样式。
  • TextInput 用于用户输入搜索内容,设置了占位符为 "搜索内容",占一定的布局权重(layoutWeight(1))以自适应宽度,背景透明等样式。
  • Text 组件显示一个特定的图标(通过 \ue679 这个 Unicode 编码对应的图标,可能是自定义字体图标集中的一个),设置了各种样式,如背景颜色、使用注册的 myFont 字体、字体大小、颜色、加粗以及特定的圆角样式等,整体看起来像是搜索按钮的样式呈现。
(3)主体内容部分(基于 List 组件)
List(){
  // 轮播图相关的 ListItem
  ListItem(){
    Swiper(){
      Image($r('app.media.img01'))
      Image($r('app.media.img02'))
      Image($r('app.media.img03'))
      Image($r('app.media.img04'))
      Image($r('app.media.img05'))
      Image($r('app.media.img06'))
    }
   .width('100%')
   .aspectRatio(2)
   .loop(true)
   .autoPlay(true)
   .interval(3000)
   .indicator(
      Indicator.dot()
       .itemWidth(10)
       .itemHeight(10)
       .selectedItemWidth(20)
       .selectedItemHeight(10)
       .color(Color.White)
       .selectedColor(Color.Red)
    )
  }
  // 网格布局分类展示的 ListItem
  ListItem(){
    Grid(){
      GridItem(){
        Column(){
          Text('\ue67d')
           .listItem()
          Text('店铺')
           .icoText()
        }
      }
      // 省略其他几个 GridItem 类似结构,都是展示不同分类
    }
   .width('100%')
   .height('100%')
   .rowsTemplate('1fr')
   .columnsTemplate('1fr 1fr 1fr 1fr')
  }
 .width('100%')
 .height(100)
 .margin({top:5,bottom:5})
  // 推荐标题的 ListItem
  ListItem(){
    Row(){
      Text('推荐好物')
       .fontSize('100%')
       .height(30)
       .fontWeight('bolder')
       .fontColor('#0966b4')
      Text('更多⇨')
       .fontSize(12)
       .fontColor('#0966b4')
    }
   .backgroundColor('#d1d1d1')
   .justifyContent(FlexAlign.SpaceBetween)
   .width('100%')
   .padding(10)
  }
  ListItem(){
    HomeProduct()
  }
}
.layoutWeight(1)
.backgroundColor(Color.White)
List 组件作为一个可滚动的列表容器,里面包含多个  ListItem,每个  ListItem 呈现不同的内容块。
  • 轮播图 ListItem
    • 内部的 Swiper 组件用于实现图片轮播效果,添加了多个 Image 组件(资源通过类似 $r('app.media.imgXX') 引用)。
    • Swiper 设置了宽度占满父容器、固定的宽高比(aspectRatio(2)),开启循环播放(loop(true))、自动播放(autoPlay(true))且设置了轮播间隔为 3000 毫秒,同时配置了轮播指示器(Indicator.dot() 相关配置),用于显示当前轮播图片的索引等信息,以小圆点形式呈现,并且区分了选中和未选中状态的样式。
  • 网格布局分类展示 ListItem
    • 使用 Grid 组件构建一个网格布局,里面有多个 GridItem,每个 GridItem 又包含 Column 布局,用于垂直排列图标(通过自定义字体图标对应的 Text 组件且应用 listItem 扩展样式)和对应的文字说明(应用 icoText 扩展样式),展示不同的分类,比如店铺、陶瓷等。
    • Grid 设置了宽度、高度占满父容器,以及行列模板,定义了一行四列且均匀分配空间的布局形式。
  • 推荐标题 ListItem
    • 通过 Row 布局包含两个 Text 组件,分别显示 "推荐好物"(样式上做了字体大小、加粗、颜色等设置)和 "更多⇨"(相对小一点字体且同样设置了颜色),整体 Row 设置了背景颜色、两端对齐(justifyContent(FlexAlign.SpaceBetween))以及内边距等样式,用于呈现一个推荐好物的标题栏效果,并且可以点击 "更多⇨" 可能跳转到更多推荐内容页面(具体取决于相关交互逻辑实现,代码中未体现)。

最后一个 ListItem 使用了导入的 HomeProduct 组件,用于展示具体的推荐好物详细内容,不过具体呈现依赖于 HomeProduct 组件自身的实现。
 

  • 自定义文本样式扩展函数
@Extend(Text)function listItem(){
 .width(60)
 .height(60)
 .backgroundColor('#0966b4')
 .fontFamily('myFont')
 .fontSize(35)
 .fontColor('#fff')
 .borderRadius(30)
 .textAlign(TextAlign.Center)
}
@Extend(Text)function icoText(){
 .fontSize(15)
 .height(30)
 .fontWeight('bolder')
}

通过 @Extend(Text) 装饰器为 Text 组件扩展了两个自定义样式函数。

  • listItem 函数主要用于给 Text 组件设置特定的宽高、背景颜色(使用之前注册的 myFont 字体、较大字体、白色字体颜色、圆形边框以及文本居中对齐等样式,从代码使用场景来看,可能用于那些作为图标样式展示的 Text 组件)。
  • icoText 函数则是给 Text 组件设置相对小一点的字体大小、固定高度以及加粗字体样式,用于那些配合图标展示的文字说明部分,使整体界面文字显示更规范统一且美观。

3. 在HomePruduct.ets文件中定义 HomeProduct 组件,展示商城主页中的内容。

实现代码如下:

interface  Data{
  src:ResourceStr
  txt:string
  price:number
}
@Component
export default struct HomeProduct{
  @State datas: Data[] = []
  @State template: string = '1fr 1fr'

  aboutToAppear(): void {
    for(let i=1;i<=20;i++){
      this.datas.push({
        src:i%2==0? $r('app.media.product7'):$r('app.media.product1'),
        txt:'陶瓷产品'+i,
        price:15
      })
    }
  }
  @Builder
  getItem(src:ResourceStr,txt:string,price:number){
    Column(){
      Image(src).width('100%').borderRadius(5)
      Text(txt).fontSize(15).fontWeight(FontWeight.Bold).margin({top:10})
      Text(){
        Span('¥ ')
          .fontColor(Color.Red)
          .fontSize(10)
        Span(price?.toFixed(2))
          .fontColor(Color.Red)
          .fontWeight(FontWeight.Bold)
      }
    }.width('100%')
  }
  build() {
    Stack(){
      Column(){
        WaterFlow(){
          ForEach(this.datas,(item:Data)=>{
            FlowItem(){
              this.getItem(item.src,item.txt,item.price)
            }
          },(item:Data)=>JSON.stringify(item))
        }.columnsTemplate(this.template)
        .rowsGap(10)
        .columnsGap(10)
        .nestedScroll({
          scrollForward: NestedScrollMode.PARENT_FIRST,
          scrollBackward:NestedScrollMode.SELF_FIRST
        })
      }
      .height('100%')
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .padding(5)
    .alignContent(Alignment.Bottom)
  }
}

该组件用于展示一系列产品相关的信息,包括产品图片、名称以及价格等内容。组件内部实现了数据初始化、单个产品项的构建以及整体产品列表的布局展示等功能。
 

  • 接口定义
interface Data{
  src:ResourceStr
  txt:string
  price:number
}

定义了一个名为 Data 的接口,用于规范表示产品数据的结构。其中包含三个属性:

 
  • src:类型为 ResourceStr,推测是用于引用资源(可能是图片资源等)的一种特定类型,用于指定产品对应的图片资源。
  • txt:字符串类型,用于存放产品的文字描述,比如产品名称等相关信息。
  • price:数值类型,用于表示产品的价格信息。

  • HomeProduct 组件定义
@Component
export default struct HomeProduct{
  //...
}
使用  @Component 装饰器将  HomeProduct 结构体标记为一个组件,意味着它可作为独立的 UI 单元参与界面构建,其界面呈现由内部的  build 方法来定义,同时还有相关的状态管理和生命周期方法等。

 
  • 组件状态定义
@State datas: Data[] = []
@State template: string = '1fr 1fr'
  • @State 装饰器用于定义组件的响应式状态变量。
  • datas:是一个 Data 类型的数组,初始化为空数组,用于存储要展示的多个产品的数据信息,后续会在组件的生命周期方法中进行数据填充。
  • template:是一个字符串类型的状态变量,初始值为 '1fr 1fr',从后续使用情况看,可能用于控制产品列表布局中列的模板(比如在 WaterFlow 布局里控制列的分布比例等情况)。
     
  • aboutToAppear 生命周期方法
aboutToAppear(): void {
  for(let i=1;i<=20;i++){
    this.datas.push({
      src:i%2==0? $r('app.media.product7'):$r('app.media.product1'),
      txt:'陶瓷产品'+i,
      price:15
    })
  }
}

aboutToAppear 方法通常在组件即将显示在界面上时被触发(是组件生命周期的一部分)。

  • 在这个方法中,通过一个循环(从 1 到 20)往 datas 数组中添加模拟的产品数据。对于每个产品:
    • src 属性根据索引 i 的奇偶性来选择不同的图片资源(通过 $r('app.media.productX') 方式引用,具体资源加载机制依赖框架实现),这里简单地实现了交替使用两种图片资源来模拟不同产品的图片。
    • txt 属性设置为 '陶瓷产品' 加上当前的索引值,形成一个简单的产品名称描述。
    • price 属性统一设置为 15,模拟产品价格。

  • getItem 构建函数
@Builder
getItem(src:ResourceStr,txt:string,price:number){
  Column(){
    Image(src).width('100%').borderRadius(5)
    Text(txt).fontSize(15).fontWeight(FontWeight.Bold).margin({top:10})
    Text(){
      Span('¥ ')
       .fontColor(Color.Red)
       .fontSize(10)
      Span(price?.toFixed(2))
       .fontColor(Color.Red)
       .fontWeight(FontWeight.Bold)
    }
  }.width('100%')
}

使用 @Builder 装饰器,表明这是一个用于构建 UI 片段的函数,它接收产品的相关数据(图片资源、名称、价格)作为参数来构建一个产品项的 UI 结构。

  • 内部通过 Column(列布局)来组织产品项的内容:
    • 首先是一个 Image 组件,使用传入的 src 参数来显示产品图片,设置宽度占满父容器并且添加了圆角样式(borderRadius(5)),使其显示更美观。
    • 接着是一个 Text 组件,用于显示产品的名称(传入的 txt 参数),设置了字体大小为 15,加粗字体(FontWeight.Bold)以及顶部有一定的外边距,使其与图片有间隔。
    • 然后又是一个 Text 组件,内部使用了 Span 来分别构建价格显示的两部分(货币符号和具体价格数值),货币符号部分设置了红色字体颜色、较小的字体大小,价格数值部分同样设置为红色字体颜色并且加粗字体,整体用于清晰美观地展示产品价格信息,并且整个列布局宽度占满父容器。

  • build 方法(核心界面构建逻辑)
build() {
  Stack(){
    Column(){
      WaterFlow(){
        ForEach(this.datas,(item:Data)=>{
          FlowItem(){
            this.getItem(item.src,item.txt,item.price)
          }
        },(item:Data)=>JSON.stringify(item))
      }.columnsTemplate(this.template)
     .rowsGap(10)
     .columnsGap(10)
     .nestedScroll({
        scrollForward: NestedScrollMode.PARENT_FIRST,
        scrollBackward:NestedScrollMode.SELF_FIRST
      })
    }
   .height('100%')
   .width('100%')
  }
 .width('100%')
 .height('100%')
 .padding(5)
 .alignContent(Alignment.Bottom)
}
 

整个界面构建基于 Stack(层叠布局),里面包含一个 Column(列布局),用于组织产品列表等内容。

  • Column 内部使用了 WaterFlow(瀑布流布局)组件来展示产品列表:
    • 通过 ForEach 循环遍历 datas 数组中的每个产品数据项(Data 类型),对于每个数据项,在 FlowItem 中调用 getItem 函数来构建对应的产品项 UI 结构,从而实现根据数据动态生成多个产品展示项的效果。同时传递了一个用于唯一标识每个数据项的函数(这里简单地将数据项转换为 JSON 字符串来作为标识)。
    • WaterFlow 组件设置了 columnsTemplate 为 this.template,即根据前面定义的 template 状态变量来确定列的布局模板(比如列的宽度分配比例等情况),设置了行与列之间的间隔(rowsGap(10) 和 columnsGap(10)),并且配置了嵌套滚动相关的模式(nestedScroll),用于处理滚动行为,比如规定了向前滚动(scrollForward)和向后滚动(scrollBackward)时采用的滚动模式(分别是 PARENT_FIRST 和 SELF_FIRST,涉及到和父容器滚动交互等情况)。
  • 外层的 Column 设置了高度和宽度占满父容器,而最外层的 Stack 同样设置了宽度和高度占满父容器,并且添加了一定的内边距(padding(5))以及内容对齐方式为底部对齐(alignContent(Alignment.Bottom)),整体构建出产品列表展示的完整界面布局结构。

4. 在src/main/ets/pages/Index.ets文件中,定义Index 组件作为应用的入口组件。

实现代码如下:

import font from '@ohos.font'
import Home from "../components/Home"
@Entry
@Component
struct Index {
  aboutToAppear(): void {
    font.registerFont({
      familyName: 'myFont',
      familySrc: '/fonts/iconfont.ttf'
    })
  }

  @State selectedIndex: number = 0

  @Builder
  myBuilder(itemIndex: number, title: string, ico: string) {
    Column() {
      Text(ico)
        .width(30)
        .height(30)
        .fontFamily('myFont')
        .fontSize(30)
        .textAlign(TextAlign.Center)
        .fontColor(itemIndex == this.selectedIndex ?'#fa2a83' : Color.Black)
      Text(title)
        .fontColor(itemIndex == this.selectedIndex ? '#fa2a83' : Color.Black)
    }
  }

  build() {
    Tabs({ barPosition: BarPosition.End }) {
      TabContent() {
        Home()
      }
      .tabBar(this.myBuilder(0, '首页', '\ue64c'))

      TabContent() {
        Text("分类内容")
      }
      .tabBar(this.myBuilder(1, '分类', '\ue626'))

      TabContent() {
        Text("购物内容")
      }
      .tabBar(this.myBuilder(2, '购物', '\ue604'))

      TabContent() {
        Text("我的内容")
      }
      .tabBar(this.myBuilder(3, '我的', '\ue61e'))
    }

    .onChange((index: number) => {
      this.selectedIndex = index
    })

  }
}

定义了一个名为 Index 的组件,它作为应用的入口组件(通过 @Entry 装饰器标识),构建了一个带有底部导航栏(通过 Tabs 组件实现)的界面结构,导航栏包含多个选项卡,点击不同选项卡可切换显示不同的内容页面,同时在切换时还实现了相应的状态更新及样式变化等功能。

  • 导入模块
import font from '@ohos.font'
import Home from "../components/Home"

        从 @ohos.font 导入了 font 模块,大概率用于字体相关操作,后续代码中会使用它来注册自定义字体,以满足界面中特定字体显示需求。

        导入了自定义的 Home 组件,从代码结构推测,Home 组件应该是展示应用首页相关内容的一个独立组件,这里会被整合到 Tabs 所构建的多页面切换体系当中。

  • Index 组件定义与入口标识
@Entry
@Component
struct Index {
  //...
}
        使用  @Entry 装饰器将  Index 结构体标记为整个应用的入口组件,意味着应用启动时会首先渲染这个组件所定义的界面内容。同时, @Component 装饰器表明它是一个符合组件规范的 UI 单元,其界面呈现由内部的  build 方法来确定。

  • aboutToAppear 生命周期方法
aboutToAppear(): void {
  font.registerFont({
    familyName: 'myFont',
    familySrc: '/fonts/iconfont.ttf'
  })
}

        这是组件生命周期中在即将显示时触发的方法。在这里调用了 font 模块的 registerFont 方法,注册了一个名为 myFont 的自定义字体,字体文件来源指定为 /fonts/iconfont.ttf。注册这个字体后,后续就可以在界面中使用该字体来展示特定的文本样式了,例如显示一些自定义的图标字体等内容。

  • 组件状态定义
@State selectedIndex: number = 0
        通过  @State 装饰器定义了一个名为  selectedIndex 的响应式状态变量,其初始值设置为  0。这个变量用于记录当前选中的选项卡索引,在后续选项卡切换以及相应 UI 样式更新时会起到关键作用,因为界面上需要根据当前选中的选项卡来展示不同的样式效果,比如改变图标和文字的颜色等。

  • myBuilder 构建函数
@Builder
myBuilder(itemIndex: number, title: string, ico: string) {
  Column() {
    Text(ico)
     .width(30)
     .height(30)
     .fontFamily('myFont')
     .fontSize(30)
     .textAlign(TextAlign.Center)
     .fontColor(itemIndex == this.selectedIndex?'#fa2a83' : Color.Black)
    Text(title)
     .fontColor(itemIndex == this.selectedIndex? '#fa2a83' : Color.Black)
  }
}

        使用 @Builder 装饰器表明这是一个用于构建 UI 片段的函数。

        该函数接收三个参数:

  • itemIndex(表示当前选项卡的索引)
  • title(选项卡对应的标题文本)
  • ico(用于显示的图标对应的字符编码,通常结合自定义字体来显示图标样式),并基于这些参数构建一个包含图标和标题文本的 Column(列布局)UI 结构。

        对于图标对应的 Text 组件:

        设置了固定的宽度和高度(width(30) 和 height(30)),指定使用之前注册的 myFont 字体,字体大小为 30,文本居中对齐(textAlign(TextAlign.Center)),并且关键的是,根据当前选项卡索引(itemIndex)与记录选中索引的 selectedIndex 是否相等,来动态设置字体颜色,如果相等则显示为 #fa2a83 颜色(可能是一种突出显示的颜色,用于标识选中状态),否则显示为黑色(普通未选中状态的颜色)。

        对于标题文本对应的 Text 组件,同样根据索引是否相等来动态设置字体颜色,以实现选中和未选中状态下文字颜色的不同显示效果,整体通过这个函数构建出每个选项卡对应的底部导航栏子项的展示样式。

  • build 方法(核心界面构建逻辑)
build() {
  Tabs({ barPosition: BarPosition.End }) {
    TabContent() {
      Home()
    }
   .tabBar(this.myBuilder(0, '首页', '\ue64c'))

    TabContent() {
      Text("分类内容")
    }
   .tabBar(this.myBuilder(1, '分类', '\ue626'))

    TabContent() {
      Text("购物内容")
    }
   .tabBar(this.myBuilder(2, '购物', '\ue604'))

    TabContent() {
      Text("我的内容")
    }
   .tabBar(this.myBuilder(3, '我的', '\ue61e'))
  }
.onChange((index: number) => {
    this.selectedIndex = index
  })
}

   整个界面构建基于 Tabs 组件,用于创建多选项卡切换的布局效果,并且通过 { barPosition: BarPosition.End } 参数设置选项卡栏的位置为底部(BarPosition.End 表示底部位置,还有其他可能的位置选项如顶部等)。

        在 Tabs 组件内部,有多个 TabContent 子组件,每个 TabContent 对应一个选项卡的内容页面:

  • 第一个 TabContent 中放置了之前导入的 Home 组件,作为应用的首页内容展示,并且通过 .tabBar(this.myBuilder(0, '首页', '\ue64c')) 调用 myBuilder 函数来构建对应的底部导航栏子项样式,传入索引 0、标题 '首页' 以及对应的图标字符编码 '\ue64c',用于显示首页对应的图标和文字样式,并且能根据选中状态改变颜色。
  • 后续的几个 TabContent 结构类似,分别展示简单的文本内容(如 '分类内容''购物内容''我的内容' 等),同样通过调用 myBuilder 函数传入不同的参数来构建各自对应的底部导航栏子项样式,每个选项卡都有自己对应的图标和文字,以及相应的选中 / 未选中状态样式变化。
  • 最后,通过 .onChange((index: number) => { this.selectedIndex = index }) 为 Tabs 组件注册了一个选项卡切换的回调函数,当用户点击切换选项卡时,会触发这个回调,将当前选中的选项卡索引更新到 selectedIndex 这个状态变量中,这样就能实时根据选中情况更新界面上相关元素(如底部导航栏图标和文字颜色)的样式了,保证 UI 展示与用户操作的一致性。

👋实验小结

        本次实验成功构建了具有首页及底部导航栏多页面切换功能的应用界面。首页包含搜索栏、轮播图、分类网格与推荐好物列表等丰富内容,底部导航栏切换流畅且能实现选中状态样式更新。在技术层面,深入理解并运用组件化开发提升代码可维护性与复用性,像 Home 和 HomeProduct 组件各司其职;通过 @ohos.font 模块注册自定义字体用于图标展示,增强界面特色;灵活采用多种布局组件构建复杂结构,如 Column、Row 等布局的巧妙嵌套;借助响应式状态变量与数据循环达成数据驱动 UI,确保数据与界面显示一致。实验中遇到字体资源加载、布局适配及数据与 UI 同步等问题,均通过仔细检查路径、优化布局属性设置及遵循响应式编程最佳实践得以解决。此次实验收获颇丰,不仅熟练掌握组件化、布局构建与数据驱动等关键技术,还提升了问题解决能力,为后续应用开发积累了宝贵经验。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/943073.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

连锁餐饮行业数据可视化分析方案

引言 随着连锁餐饮行业的迅速发展&#xff0c;市场竞争日益激烈。企业需要更加精准地把握运营状况、消费者需求和市场趋势&#xff0c;以制定科学合理的决策&#xff0c;提升竞争力和盈利能力。可视化数据分析可以帮助连锁餐饮企业整合多源数据&#xff0c;通过直观、动态的可…

学习threejs,THREE.RingGeometry 二维平面圆环几何体

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.RingGeometry 圆环几…

计算机网络实验室建设方案

一、计算机网络实验室拓扑结构 计算机网络综合实验室解决方案&#xff0c;是面向高校网络相关专业开展教学实训的综合实训基地解决方案。教学实训系统采用 B&#xff0f;S架构&#xff0c;通过公有云教学实训平台在线学习模式&#xff0c;轻松实现网络系统建设与运维技术的教学…

ThinkPHP 8开发环境安装

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《ThinkPHP 8高效构建Web应用 夏磊 编程与应用开发丛书 清华大学出版社》【摘要 书评 试读】- 京东图书 1. 安装PHP8 Windows系统用户可以前往https://windows.php.net/downloads/releases/archives/下载PHP 8.0版本&am…

Nmap基础入门及常用命令汇总

Nmap基础入门 免责声明&#xff1a;本文单纯分享技术&#xff0c;请大家使用过程中遵守法律法规~ 介绍及安装 nmap是网络扫描和主机检测的工具。作为一个渗透测试人员&#xff0c;必不可少的就是获取信息。那么nmap就是我们从互联网上获取信息的途径&#xff0c;我们可以扫描互…

使用openvino加速部署paddleocr文本方向分类模型(C++版)

使用openvino加速部署paddleocr文本方向分类模型(C++版) 大体流程方向分类器在openvino上的部署代码C++大体流程 原始图像: 先resize 再归一化 方向分类器在openvino上的部署代码C++ #include <iostream> #include <string>#include <vector> #i…

嵌入式单片机窗口看门狗控制与实现

窗口看门狗 注意:WWDG外设没有独立的时钟源,而是挂载在APB1总线下,APB1总线外设时钟为42MHZ。 了解WWDG外设的使用流程,可以参考stm32f4xx_wwdg.c的开头注释,具体流程如下图所示

【KLEE】源码阅读笔记----KLEE执行流程

本文架构 1. 动机2.KLEE简介3.KLEE的代码工程结构4. 从KLEE主函数入手main函数step1: 初始化step2&#xff1a;加载.bc文件进行符号执行 读取测试用例输出日志信息 1. 动机 最近准备对KLEE进行修改使其符合我的需要&#xff0c;因此免不了需要对源码进行修改。读懂源码是对在其…

【hackmyvm】soul靶机wp

tags: HMVrbash绕过图片隐写PHP配置解析 1. 基本信息^toc 文章目录 1. 基本信息^toc2. 信息收集3. 图片解密3.1. 爆破用户名3.2. 绕过rbash3.3. 提权检测 4. 获取webshell4.1. 修改php配置 5. www-data提权gabriel6. gabriel提取到Peter7. Peter提权root 靶机链接 https://ha…

macos 隐藏、加密磁盘、文件

磁盘加密 打开磁盘工具 点击添加 设置加密参数 设置密码 查看文件 不用的时候右键卸载即可使用的时候装载磁盘&#xff0c;并输入密码即可 修改密码 解密 加密&#xff0c;输入密码即可 禁止开机自动挂载此加密磁盘 如果不禁止自动挂载磁盘&#xff0c;开机后会弹出输入…

基于OpenCV和Python的人脸识别系统_django

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 公告信息管理 操作日志管理 用户登录界面 用户…

如何永久解决Apache Struts文件上传漏洞

Apache Struts又双叒叕爆文件上传漏洞了。 自Apache Struts框架发布以来&#xff0c;就存在多个版本的漏洞&#xff0c;其中一些漏洞涉及到文件上传功能。这些漏洞可能允许攻击者通过构造特定的请求来绕过安全限制&#xff0c;从而上传恶意文件。虽然每次官方都发布补丁进行修…

回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归…

LeetCode - Google 校招100题 第5天 双指针(Two Pointers) (11题)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144742777 LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (20题)

【无人机】无人机测绘路径优化策略与实践:探索高效、精准的测绘技术路径

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;传知代码 欢迎大家点赞收藏评论&#x1f60a; 目录 一、背景介绍二、算法原理&#xff08;一&#xff09;算法模型构建&#xff08;二&#xff09;算法求解流程 三、代码实现&#xff08;一&…

Anaconda搭建Python虚拟环境并在Pycharm中配置(小白也能懂)

为什么要搭建虚拟环境&#xff1f; 搭建虚拟环境的主要目的是为了解决多个Python项目之间可能存在的库冲突问题。当你在同一台计算机上运行多个Python项目时&#xff0c;不同的项目可能会依赖于不同版本的库或者相同版本的库的不同补丁。如果所有项目都共享相同的Python环境&am…

mac_录屏

参考&#xff1a; mac m1上系统内录方法BlackHole代替soundflower录音(附安装包) https://blog.csdn.net/boildoctor/article/details/122765119录屏后没声音&#xff1f;这应该是 Mac&#xff08;苹果电脑&#xff09; 内录声音最优雅的解决方案了 https://www.bilibili.com/…

upload-labs关卡记录13

这里和关卡12非常类似&#xff0c;唯一不同就是12关用到get方法&#xff0c;这里用到post方法。因此对应的截断方式也不一样&#xff0c;依旧是使用我们的bp进行抓包&#xff0c; 然后依旧是在upload后加上shell.php&#xff0c;这里用是为了hex时好区别我们要在哪里更改&#…

网络管理-期末项目(附源码)

环境&#xff1a;网络管理 主机资源监控系统项目搭建 &#xff08;保姆级教程 建议点赞 收藏&#xff09;_搭建网络版信息管理系统-CSDN博客 效果图 下面3个文件的项目目录(python3.8.8的虚拟环境) D:\py_siqintu\myproject5\Scripts\mytest.py D:\py_siqintu\myproject5\Sc…

通过GRE协议组建VPN网络

GRE&#xff08;Generic Routing Encapsulation&#xff0c;通用路由封装协议&#xff09;协议是一种简单而有效的封装协议&#xff0c;它在网络中的广泛应用&#xff0c;比如在构建VPN网络。   GRE是一种封装协议&#xff0c;它允许网络层协议&#xff08;如IP&#xff09;的…