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

目录

😋环境配置:华为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/940897.html

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

相关文章

智能体实战(需求分析助手)二、需求分析助手第一版实现(支持需求提取、整理、痛点分析、需求分类、优先级分析、需求文档生成等功能)

基于提供的调用 qwen-plus 大模型的实战代码&#xff0c;我将对需求分析助手的第一迭代功能目标进行实现设计。以下是基于该示例代码的第一迭代功能实现细化方案&#xff1a; 功能 1&#xff1a;用户与需求分析助手交互界面&#xff08;文本交互&#xff09; 实现步骤&#xf…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是&#xff0c;则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时&#xff0c;Mihomo网卡会消失&#xff0c;所以提示找不到网卡…

malloc 分配大堆块(128KB)的一次探索

前言 一次意外执行了 malloc(0x5000)&#xff0c;结构使用 gdb 调试发现其分配的位置在 TLS 区域&#xff0c;这令我不解&#xff08;&#xff1a;最后去看了下 malloc 源码和 mmap 源码实现&#xff0c;发现似乎可能是 gdb 插件的问题&#xff0c;乐 场景复现 #include <…

大数据机器学习算法和计算机视觉应用07:机器学习

Machine Learning Goal of Machine LearningLinear ClassificationSolutionNumerical output example: linear regressionStochastic Gradient DescentMatrix Acceleration Goal of Machine Learning 机器学习的目标 假设现在有一组数据 x i , y i {x_i,y_i} xi​,yi​&…

【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 引言 图像增强API调用实践1. API选择与参数设置2. 在线调试与结果分析3. 响应结果具体实现代码 发票…

android studio更改应用图片,和应用名字。

更改应用图标&#xff0c;和名字 先打开AndroidManifest.xml文件。 更改图片文件名字&#xff08; 右键-->构建-->重命名&#xff08;R&#xff09;&#xff09;

Git(11)之log显示支持中文

Git(11)之log显示支持中文 Author&#xff1a;Once Day Date&#xff1a;2024年12月21日 漫漫长路有人对你微笑过嘛… 参考文档&#xff1a;GIT使用log命令显示中文乱码_gitlab的log在matlab里显示中文乱码-CSDN博客 全系列文章可查看专栏: Git使用记录_Once_day的博客-CSD…

代理模式(JDK,CGLIB动态代理,AOP切面编程)

代理模式是一种结构型设计模式&#xff0c;它通过一个代理对象作为中间层来控制对目标对象的访问&#xff0c;从而增强或扩展目标对象的功能&#xff0c;同时保持客户端对目标对象的使用方式一致。 代理模式在Java中的应用,例如 1.统一异常处理 2.Mybatis使用代理 3.Spring…

猪猪软件库

猪猪软件库&#xff0c;汇聚各类宝藏软件&#xff0c;宛如一座数字百宝箱。这里涵盖了实用工具、趣味游戏、高效办公软件等丰富资源&#xff0c;满足不同用户的多样需求。界面简洁直观&#xff0c;搜索便捷流畅&#xff0c;能让你迅速定位心仪软件。 所有资源都经过严格筛选&a…

端到端自动驾驶大模型:视觉-语言-动作模型 VLA

模型框架定义、模型快速迭代能力是考查智驾团队出活能力的两个核心指标。在展开讨论Vision-Language-Action Models(VLA)之前&#xff0c;咱们先来讨论端到端自动驾驶大模型设计。 目录 1. 端到端自动驾驶大模型设计 1.1 模型输入设计 1.2 模型输出设计 1.3 实现难点分析 …

Swin transformer 论文阅读记录 代码分析

该篇文章&#xff0c;是我解析 Swin transformer 论文原理&#xff08;结合pytorch版本代码&#xff09;所记&#xff0c;图片来源于源paper或其他相应博客。 代码也非原始代码&#xff0c;而是从代码里摘出来的片段&#xff0c;配上简单数据&#xff0c;以便理解。 当然&…

LLMs之rStar:《Mutual Reasoning Makes Smaller LLMs Stronger Problem-Solvers》翻译与解读

LLMs之rStar&#xff1a;《Mutual Reasoning Makes Smaller LLMs Stronger Problem-Solvers》翻译与解读 导读&#xff1a;这篇论文提出了一种名为rStar的自我博弈互推理方法&#xff0c;用于增强小型语言模型 (SLMs) 的推理能力&#xff0c;无需微调或依赖更强大的模型。rStar…

CS 144 check5: down the stack (the network interface)

Lectures Note 略 Exercises TCP片段传输到对等方的过程&#xff1a; TCP-in-UDP-in-IP. Linux 提供了一种接口&#xff08;即“数据报套接字”&#xff0c;UDPSocket&#xff09;&#xff0c;它允许应用程序仅提供用户数据报的有效载荷和目标地址&#xff0c;而内核则负责…

Llama 3 模型系列解析(一)

目录 1. 引言 1.1 Llama 3 的简介 1.2 性能评估 1.3 开源计划 1.4 多模态扩展 ps 1. 缩放法则 2. 超额训练&#xff08;Over-training&#xff09; 3. 计算训练预算 4. 如何逐步估算和确定最优模型&#xff1f; 2. 概述 2.1 Llama 3 语言模型开发两个主要阶段 2.2…

越疆科技营收增速放缓:毛利率未恢复,持续亏损下销售费用偏高

《港湾商业观察》施子夫 12月13日&#xff0c;深圳市越疆科技股份有限公司&#xff08;以下简称&#xff0c;越疆科技&#xff0c;02432.HK&#xff09;发布全球发售公告&#xff0c;公司计划全球发售4000万股股份&#xff0c;其中3800万股国际发售&#xff0c;200万股香港公开…

微信小程序实现画板画布自由绘制、选择画笔粗细及颜色、记录撤回、画板板擦、清空、写字板、导出绘图、canvas,开箱即用

目录 画板创建canvas绘制及渲染画笔粗细功能实现画笔颜色选择画笔痕迹撤回、板擦、画布清空canvas解析微信小程序中 canvas 的应用场景canvas 与 2D 上下文、webgl 上下文的关系图像的加载与绘制说明代码说明画板创建 canvas绘制及渲染 在wxml添加对应的canvas标签代码,并在j…

混合精度训练说明

什么是混合精度训练&#xff1f;混合精度训练有什么用&#xff1f; 这里总结一下。 本文总结自kapathy的build gpt2 通常在训练过程中&#xff0c;model里面的数据默认都是torch.float32类型&#xff0c; 也就是用32bit的float型数据表示变量。 比如特征提取中提取的特征&…

draw.io 导出svg图片插入word后模糊(不清晰 )的解决办法

通常我们将图片从draw.io导出为svg格式后插入word, 会发现字体不清晰&#xff0c;特别是使用宋体时&#xff0c;折腾了半天&#xff0c;得到如下办法&#xff1a; 方法1: 在draw.io中导出pdf文件&#xff0c;使用 PDF转SVG转换器 - SVGConverter 将其转换为svg, 完美呈现。 …

ARM学习(38)多进程多线程之间的通信方式

ARM学习(38)ARM学习(38)多进程多线程之间的通信方式 一、问题背景 笔者在调试模拟器的时候,碰到进程间通信的问题,一个进程在等另外一个进程ready的时候,迟迟等不到,然后通过调试发现,另外一个进程变量已经变化了,但是当前进程变量没变化,需要了解进程间通信的方式…

【动手学运动规划】 5.2 数值优化基础:梯度下降法,牛顿法

朕四季常服, 不过八套. — 大明王朝1566 道长 &#x1f3f0;代码及环境配置&#xff1a;请参考 环境配置和代码运行! 上一节我们介绍了数值优化的基本概念, 让大家对最优化问题有了基本的理解. 那么对于一个具体的问题, 我们应该如何求解呢? 这一节我们将介绍几个基本的求解…