HarmonyOs 应用基础--Scroll-Tabs-Badge

目录

十. Scroll、Tabs、Badge

1. 容器组件 Scroll

1.1. 核心用法

 1.2. 案例-小米有品

1.3. Scroll 的控制器

 1.3.1. 基础模版

1.3.2. scrollEdge方法 

1.3.3. currentOffset 方法

 1.3.4. 参考代码

1.4. Scroll 事件

1.5. 案例-京东

2. 容器组件Tabs

 2.1. 基本用法

2.2. 常用属性

2.3. 滚动导航栏

2.4. 自定义tabBar

2.4.1. 自定义tabBar-自定义外观

2.4.2. 自定义 tabBar-Tabs组件的事件

2.4.3. 自定义 tabBar-高亮切换

 2.4.3.1. 基础模版

 2.4.3.2. 参考代码:

2.4.4. 自定义 tabBar-优化参数

2.5. 案例-小米有品

2.5.1. 基础模版

 2.5.2. 参考答案

3. 容器组件Badge

 3.1. 核心用法

 3.2. 案例-小米有品-购物车

 3.2.1. 基础模版

 3.2.2. 参考代码


十. Scroll、Tabs、Badge

今日核心:

  • 容器组件:Scroll、Tabs、Badge

1. 容器组件 Scroll

可滚动的容器组件,当子组件的布局尺寸超过Scroll的尺寸时,内容可以滚动。

当页面内容由多个区域组成,并且可以滚动时,推荐使用 Scroll,比如:

  1. 小米有品:页面滚动,点击顶部区域,返回顶部
  2. 京东:页面滚动,点击右下角小火箭,返回顶部

等等....

1.1. 核心用法

先来看看 Scroll 的最核心用法,让内容滚动

核心用法:

  1. Scroll 设置尺寸
  2. 设置 子组件(只支持一个子组件)
  3. 设置滚动:
    1. 竖向滚动:子组件的高度超出 Scroll
    2. 横向滚动:子组件的宽度超出 Scroll,scrollable改为横向滚动
  1. 根据需求调整属性
Scroll(){
  // 只支持一个子组件
  Column(){
    // 内容放在内部
    // 尺寸超过 Scroll 即可滚动
  }
}
.width('100%')
.height(200)

名称

参数类型

描述

scrollable

ScrollDirection

设置滚动方向。

ScrollDirection.Vertical 纵向

ScrollDirection.Horizontal 横向

scrollBar

BarState

设置滚动条状态。

scrollBarColor

string | number | Color

设置滚动条的颜色。

scrollBarWidth

string | number

设置滚动条的宽度

edgeEffect

value:EdgeEffect

设置边缘滑动效果。

EdgeEffect.None 无

EdgeEffect.Spring 弹簧

EdgeEffect.Fade 阴影

试一试:

  1. 测试横向和竖向滚动
  2. 关闭滚动条

 基础模版

// 掌握: Scroll 组件的基本使用
// 作用: 指定某个区域可以滚动
// 注意:
//    1. Scroll只能有一个子组件
//    2. Scroll的子组件的高度 或 宽度, 要超过Scroll, 才会出现滚动
// 属性:
//   1. scrollable(枚举.水平) 调整滚动方向
@Entry
@Component
struct Page01_Scroll {
  build() {
    Column({ space: 10 }) {
      Text('竖向滚动')
        .fontSize(20)

      // Scroll 容器尺寸固定
      // 内容横向超出 Scroll 即可滚动
      Scroll() {
       
      }
      .width('100%')
      .height(200)


      Text('横向滚动')
        .fontSize(20)

      // Scroll 容器尺寸固定
      // 内容横向超出 Scroll 即可滚动
      Scroll() {

      }
      // 横向滚动
      .width('100%')
      .height(200)
    }
    .width('100%')
    .height('100%')
  }
}

 参考代码:

// 掌握: Scroll 组件的基本使用
// 作用: 指定某个区域可以滚动
// 注意:
//    1. Scroll只能有一个子组件
//    2. Scroll的子组件的高度 或 宽度, 要超过Scroll, 才会出现滚动
// 属性:
//   1. scrollable(枚举.水平) 调整滚动方向


@Entry
@Component
struct Page01_Scroll {
  build() {
    Column({ space: 10 }) {
      Text('竖向滚动')
        .fontSize(20)

      // Scroll 容器尺寸固定
      // 内容横向超出 Scroll 即可滚动
      Scroll() {
        Text()
          .width('100%')
          .height(400)
          .backgroundColor(Color.Pink)

      }
      .width('100%')
      .height(200)


      Text('横向滚动')
        .fontSize(20)

      // Scroll 容器尺寸固定
      // 内容横向超出 Scroll 即可滚动
      Scroll() {
        Text()
          .width('200%')
          .height(400)
          .backgroundColor(Color.Orange)
      }
      // 横向滚动
      .width('100%')
      .height(200)
      .scrollable(ScrollDirection.Horizontal)
    }
    .width('100%')
    .height('100%')
  }
}

 1.2. 案例-小米有品

 接下来结合 Scroll 来实现小米有品首页的滚动效果

 

需求:

  1. 使用 Scorll 实现页面滚动滚动
  2. 关闭滚动条
  3. 滚动屏幕边缘 弹簧效果

 基础模版:

@Entry
@Component
struct Page02_ScrollDemo_Xiaomi {
  build() {
    Column() {
      Image($r('app.media.ic_xiaomi_scroll_01'))
        .width('100%')
      Image($r('app.media.ic_xiaomi_scroll_02'))
        .width('100%')
      Image($r('app.media.ic_xiaomi_scroll_03'))
        .width('100%')
      
    }
  }
}

 参考答案:

@Entry
@Component
struct Page02_ScrollDemo_Xiaomi {
  build() {
    Scroll() {
      Column() {
        Image($r('app.media.ic_xiaomi_scroll_01'))
          .width('100%')
        Image($r('app.media.ic_xiaomi_scroll_02'))
          .width('100%')
        Image($r('app.media.ic_xiaomi_scroll_03'))
          .width('100%')
      }
    }
    .scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.Fade)

  }
}

1.3. Scroll 的控制器

日常开发中可能需要通过代码控制滚动,以及获取滚动的距离,比如下图:

这个时候就可以通过 Scroll 的控制器来实现

比如:

  1. 页面滚动超过一定距离,显示返回顶部(火箭),反之隐藏--获取滚动距离
  2. 点击返回顶部(火箭),返回顶部--代码控制滚动

 

 1.3.1. 基础模版
@Entry
@Component
struct Page03_Scroller {
  build() {
    Column({ space: 10 }) {
      Text('竖向滚动')
        .fontSize(20)
      Scroll() {
        // 设置内容
        Column() {
        }
        .height(1000)
        .width('100%')
        .linearGradient({
          colors: [['#0094ff', 0], [Color.Orange, 1]],
        })
      }
      .scrollBarColor(Color.Pink)
      .scrollBarWidth(5)
      .scrollBar(BarState.On)
      .edgeEffect(EdgeEffect.Spring)
      .width('100%')
      .height(200)
      .border({ width: 1, color: Color.Orange })

    }
    .width('100%')
    .height('100%')
  }
}

核心步骤:

  1. 实例化 Scroller的 控制器
  2. 绑定给 Scroll
  3. 调用 控制器的方法 控制滚动 以及 获取滚动距离
@Entry
@Component
struct Page03_Scroller {
  // 1. 创建 Scroller 控制器
  scroller = new Scroller()

  build() {
    Column() {
      // 2. 设置给Scroll
      Scroll(this.scroller) {
        // 内容略
      }
    }
  }
}

这里用到了 2 个方法:

  1. scrollEdge:滚动到边缘
  2. currentOffset返回当前的偏移量
1.3.2. scrollEdge方法 

滚动到容器边缘,不区分滚动轴方向,Edge.Top和Edge.Start表现相同,Edge.Bottom和Edge.End表现相同。 

this.scroller.scrollEdge(Edge.Top)
this.scroller.scrollEdge(Edge.Start)
this.scroller.scrollEdge(Edge.Bottom)
this.scroller.scrollEdge(Edge.End)

参数名

参数类型

必填

参数描述

value

Edge

滚动到的边缘位置。

Edge.Top 顶部

Edge.Start 开头

Edge.Bottom 底部

Edge.End 结尾

1.3.3. currentOffset 方法

作用:获取滚动距离

this.scroller.currentOffset().xOffset // x 轴滚动距离
this.scroller.currentOffset().yOffset // y 轴滚动距离

类型

描述

{

xOffset: number,

yOffset: number

}

xOffset: 水平滑动偏移;

yOffset: 竖直滑动偏移。

说明:

返回值单位为vp。

 1.3.4. 参考代码
@Entry
@Component
struct Page03_Scroller {
  scroller: Scroller = new Scroller()

  build() {
    Column({ space: 10 }) {
      Text('竖向滚动')
        .fontSize(20)
      Scroll(this.scroller) {
        // 设置内容
        Column() {
        }
        .height(1000)
        .width('100%')
        .linearGradient({
          colors: [['#0094ff', 0], [Color.Orange, 1]],
        })
      }
      .scrollBarColor(Color.Pink)
      .scrollBarWidth(5)
      .scrollBar(BarState.On)
      .edgeEffect(EdgeEffect.Spring)
      .width('100%')
      .height(200)
      .border({ width: 1, color: Color.Orange })

      //
      Row() {
        Button('滚动到顶部')
          .onClick(() => {
            this.scroller.scrollEdge(Edge.Start)
          })
        Button('滚动到底部')
          .onClick(() => {
            this.scroller.scrollEdge(Edge.Bottom)
          })
        Button('查看滚动距离')
          .onClick(() => {
            const res = this.scroller.currentOffset()
            console.log('resX -----> ', res.xOffset)
            console.log('resY -----> ', res.yOffset)

            // JSON.stringify()方法, 可以把一个对象转换为字符串, 方便数据观察
            console.log('res -----> ', JSON.stringify(res))
          })
      }

    }
    .width('100%')
    .height('100%')
  }
}

1.4. Scroll 事件

Scroll 组件提供了一些事件,让开发者可以在适当的时候添加逻辑

名称

功能描述

onScroll(event: (xOffset: number, yOffset: number) => void)

滚动事件回调, 返回滚动时水平、竖直方向偏移量。

触发该事件的条件 :

1、滚动组件触发滚动时触发,支持键鼠操作等其他触发滚动的输入设置。

2、通过滚动控制器API接口调用。

3、越界回弹。

........

更多事件参考文档,日常开发中,较为常用的是 onScroll

Scroll(){
  // 内容略
}
  .onScroll(()=>{
    // 滚动时 一直触发
    // 结合 scroller的currentOffset方法 获取滚动距离
  })

 基础模版:

@Entry
@Component
struct Page04_ScrollEvent {

  build() {
    Column({ space: 10 }) {
      Text('Scroll的事件')
        .fontSize(20)
      Scroll() {
        // 设置内容
        Image($r('app.media.ic_xiaomi_scroll_01'))
      }
      .scrollBarColor(Color.Pink) // 导航块颜色
      .scrollBarWidth(20) // 导航块宽度
      .edgeEffect(EdgeEffect.Spring) // 边缘效果
      .width('100%')
      .height(200)
      .border({ width: 1, color: Color.Orange })
     

    }
    .width('100%')
    .height('100%')
  }
}

 参考代码:

@Entry
@Component
struct Page04_ScrollEvent {
  // 1. 创建控制器对象 new + Scroller 的方式创建
  // 不需要设置 @State
  scroller: Scroller = new Scroller()

  build() {
    Column({ space: 10 }) {
      Text('Scroll的事件')
        .fontSize(20)
      Scroll(this.scroller) {
        // 设置内容
        Image($r('app.media.ic_xiaomi_scroll_01'))
      }
      .scrollBarColor(Color.Pink) // 导航块颜色
      .scrollBarWidth(20) // 导航块宽度
      .edgeEffect(EdgeEffect.Spring) // 边缘效果
      .width('100%')
      .height(200)
      .border({ width: 1, color: Color.Orange })
      // 添加 onScroll 事件
      .onScroll((x, y) => {
        console.log('yOffset', this.scroller.currentOffset()
          .yOffset)
      })

    }
    .width('100%')
    .height('100%')
  }
}

1.5. 案例-京东

接下来咱们利用刚刚学习的内容,来完成京东的返回顶部效果

需求:

  1. 布局效果(结合图片完成:ic_jd_scroll_01-03、ic_jd_scroll_tab)
    1. 内容区域滚动
    2. 底部区域
    3. 火箭始终在右下角
  1. 点击火箭(ic_jd_rocket)返回顶部
  2. 火箭显示效果切换:
    1. 默认隐藏
    2. 滚动距离超过 400 显示
    3. 滚动距离小于 400 隐藏

 参考答案:

@Entry
@Component
struct Index3 {
  sc: Scroller = new Scroller()
  @State showRocket: boolean = false

  build() {
    Column() {
      Stack({ alignContent: Alignment.BottomEnd }) {
        // 顶部滚动区域
        Scroll(this.sc) {
          Column() {
            Image($r('app.media.ic_jd_scroll_01'))
            Image($r('app.media.ic_jd_scroll_02'))
            Image($r('app.media.ic_jd_scroll_03'))
          }
        }
        .scrollBar(BarState.Off)
        .width('100%')
        .backgroundColor(Color.Orange)
        .onScroll(() => {
          if( this.sc.currentOffset().yOffset>400){
            this.showRocket=true
          }else{
            this.showRocket = false
          }

        })


        if (this.showRocket) {
          Image($r('app.media.ic_jd_rocket'))
            .width(40)
            .backgroundColor(Color.White)
            .borderRadius(20)
            .padding(5)// .margin({right:20,bottom:20})
            .offset({ x: -20, y: -20 })
            .onClick(() => {
              this.sc.scrollEdge(Edge.Top)
            })
        }

      }
      .layoutWeight(1)

      // 底部 tabbar
      Image($r('app.media.ic_jd_tab'))
        .width('100%')

    }

  }
}

2. 容器组件Tabs

当页面内容较多时,可以通过Tabs组件进行分类展示,以下这些效果都可以通过Tabs组件来实现

 

 2.1. 基本用法

 核心代码

@Entry
@Component
struct TabbarDemo {
  build() {
    Tabs() { // 顶级容器
      TabContent() {
        // 内容区域:只能有一个子组件
      }
      .tabBar('首页') // 导航栏
    }
  }
}

 尝试完成如下效果:

 

 参考答案:

@Entry
@Component
struct Page06_Tabs {
  build() {
    Tabs() {
      // 内容
      TabContent() {
        Text('首页的内容')
          .fontSize(30)
      }
      // tabBar
      .tabBar('首页')

      TabContent() {
        Text('推荐的内容')
          .fontSize(30)
      }
      .tabBar('推荐')

      TabContent() {
        Text('发现的内容')
          .fontSize(30)
      }
      .tabBar('发现')

      TabContent() {
        Text('我的内容')
          .fontSize(30)
      }
      .tabBar("我的")
    }
  }
}

2.2. 常用属性

默认的 tabs 已经可以实现切换,接下来咱们来看看如何通过属性控制他

  1. 垂直导航
  2. 导航位置
  3. 禁用滑动切换

通过 Tabs 的属性进行调整:

  • vertical 属性即可调整导航为 水平 或 垂直
  • barPosition 即可调整导航位置为 开头 或 结尾
  • scrollable 即可调整是否允许 滑动切换
  • animationDuration 设置动画时间 毫秒

基础模版 

@Entry
@Component
struct Page07_TabsAttribute {
  build() {
    Tabs() {
      // 内容
      TabContent() {
        Text('首页的内容')
          .fontSize(30)
      }
      // tabBar
      .tabBar('首页')

      TabContent() {
        Text('推荐的内容')
          .fontSize(30)
      }
      .tabBar('推荐')

      TabContent() {
        Text('发现的内容')
          .fontSize(30)
      }
      .tabBar('发现')

      TabContent() {
        Text('我的内容')
          .fontSize(30)
      }
      .tabBar("我的")
    }
  }
}

 参考代码

@Entry
@Component
struct Page07_TabsAttribute {
  build() {
    Tabs() {
      // 内容
      TabContent() {
        Text('首页的内容')
          .fontSize(30)
      }
      // tabBar
      .tabBar('首页')

      TabContent() {
        Text('推荐的内容')
          .fontSize(30)
      }
      .tabBar('推荐')

      TabContent() {
        Text('发现的内容')
          .fontSize(30)
      }
      .tabBar('发现')

      TabContent() {
        Text('我的内容')
          .fontSize(30)
      }
      .tabBar("我的")
    }
    .barPosition(BarPosition.End) // 位置
    .vertical(true) // 纵向
    .scrollable(false) // 滑动切换
    .animationDuration(4000) // 动画持续时间
  }
}

2.3. 滚动导航栏

如果导航栏的内容较多,屏幕无法容纳时,可以将他设置为滚动

 可以通过 Tabs 组件的 barMode 属性即可调整 固定导航栏 或 滚动导航栏

Tabs(){
  // 内容略
}
  .barMode(BarMode.Scrollable)// 滚动
  // .barMode(BarMode.Fixed)// 默认值

试一试:

  1. 基于模版数据 循环生成多个 TabContent
  2. 设置滚动导航栏

 基础模版:

@Entry
@Component
struct Page08_TabsScrollable {
  titles: string [] = ['首页', '关注', '热门', '军事', '体育', '八卦', '数码', '财经', '美食', '旅行']

  build() {
    // 外层容器
    Tabs() {
      // 内容
      TabContent() {
        Text('内容部分')
          .fontSize(30)
      }
      .tabBar('导航部分')

    }
  }
}

 参考代码:

@Entry
@Component
struct Page08_TabsScrollable {
  titles: string [] = ['首页', '关注', '热门', '军事', '体育', '八卦', '数码', '财经', '美食', '旅行']

  build() {
    // 外层容器
    Tabs() {
      // 内容
      ForEach(this.titles, (title: string, index: number) => {
        TabContent() {
          Text(title + '的内容')
            .fontSize(30)
        }
        .tabBar(title)
      })

    }
    .barMode(BarMode.Scrollable)
  }
}

2.4. 自定义tabBar

TabBar 如果放在底部的话,一般会显示图形文字,甚至有特殊的图标,如果要实现此类效果,就需要 自定义tabBar

2.4.1. 自定义tabBar-自定义外观

tabBar

string | CustomBuilder

设置TabBar上显示内容。

CustomBuilder: 构造器,内部可以传入组件

 核心代码

Tabs() {
    TabContent() {
        // 内容略
    }
    .tabBar(this.tabBarBuilder())
  }

@Builder
tabBarBuilder() {
  // 自定义的Tabbar结构
}

试一试:

  1. 通过自定义 tabbar 实现如下效果

 基础模版:

@Entry
@Component
struct Page09_Tabs_CustomTabBar {
  build() {
    Tabs() {
      TabContent() {
        Text('首页')
      }
      .tabBar('首页')

      TabContent() {
        Text('我的')
      }
      .tabBar('我的')
    }
    .barPosition(BarPosition.End)
  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string) {
    Column() {
      Image(img)
        .width(30)
      Text(text)
    }
  }
}

 参考代码:

@Entry
@Component
struct Page09_Tabs_CustomTabBar {
  build() {
    Tabs() {
      TabContent() {
        Text('首页')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页'))

      TabContent() {
        Text('我的')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的'))
    }
    .barPosition(BarPosition.End)
  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string) {
    Column() {
      Image(img)
        .width(30)
      Text(text)
    }
  }
}
2.4.2. 自定义 tabBar-Tabs组件的事件

自定义TabBar 之后,高亮的切换效果就没有了,需要自行实现,咱们分两步走:

  1. 明确什么时候 tab 进行了切换
  2. 更改高亮效果

名称

功能描述

onChange(event: (index: number) => void)

Tab页签切换后触发的事件。

- index:当前显示的index索引,索引从0开始计算。

滑动切换、点击切换 均会触发

 基础模版:

@Entry
@Component
struct Page10_TabsEvents {
  build() {
    Tabs() {
      TabContent() {
        Text('首页')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页'))

      TabContent() {
        Text('我的')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的'))
    }
    .barPosition(BarPosition.End)

  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string) {
    Column() {
      Image(img)
        .width(30)
      Text(text)
    }
  }
}

 参考代码:

@Entry
@Component
struct Page10_TabsEvents {
  build() {
    Tabs() {
      TabContent() {
        Text('首页')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页'))

      TabContent() {
        Text('我的')
      }
      .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的'))
    }
    .barPosition(BarPosition.End)
    // 增加事件
    .onChange((index) => {
      console.log('onChange-index:', index)
    })
  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string) {
    Column() {
      Image(img)
        .width(30)
      Text(text)
    }
  }
}
2.4.3. 自定义 tabBar-高亮切换

结合刚刚学习的事件,来实现高亮的切换效果

 

核心思路:

  1. 用状态变量保存,onChange,ontabBarClick中获取到的索引值
  2. 每个 TabBar 传递索引值,0,1....(调整 Builder 的参数)
  3. 在tabBar内部比较 标记==this.index?高亮:不高亮
    1. 默认文字颜色和高亮文字颜色(三元表达式)
    2. 默认图片和高亮图片(调整 Builder 参数+三元表达式)
 2.4.3.1. 基础模版
@Entry
@Component
  // 高亮tabBar
struct Page11_Tabs_HighlightTabBar {

  build() {
    Column() {
      Text('当前选中的索引为:' )
      Tabs() {
        TabContent() {
          Text('首页')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页'))

        TabContent() {
          Text('我的')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的'))
      }
      .layoutWeight(1)
      .barPosition(BarPosition.End)
      // 增加事件
      .onChange((index) => {
        // tab 切换事件
      })
    }

  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string) {
    Column() {
      Image(img)
        .width(30)
      Text(text)
    }
  }
}
 2.4.3.2. 参考代码:
@Entry
@Component
  // 高亮tabBar
struct Page11_Tabs_HighlightTabBar {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Column() {
      Text('当前选中的索引为:' + this.selectedIndex)
      Tabs() {
        TabContent() {
          Text('首页')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页', 0, $r('app.media.ic_tabbar_icon_0_selected')))

        TabContent() {
          Text('我的')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的', 1, $r('app.media.ic_tabbar_icon_3_selected')))
      }
      .layoutWeight(1)
      .barPosition(BarPosition.End)
      // 增加事件
      .onChange((index) => {
        // 如果是滑动 Tabs 那么久需要用到 onChange 事件
        this.selectedIndex = index
      })
    }

  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string, index: number, selectedImg: ResourceStr) {
    Column() {
      Image(this.selectedIndex == index ? selectedImg : img)
        .width(30)
      Text(text)
        .fontColor(this.selectedIndex == index ? '#efc07e' : Color.Black)
    }
  }
}

2.4.4. 自定义 tabBar-优化参数

目前 builder 中的参数有 4 个,需要严格按照顺序传递,咱们来优化一下

需求:

  1. 使用对象作为 tabBarBuilder 的参数
  2. 对象中设置 默认图片、高亮图片、文本、索引值

核心步骤:

  1. 定义 interface
  2. 调整 tabBarBuilder
    1. 参数:形参,实参
    2. 内部逻辑:改为通过对象获取属性

 基础模版

@Entry
@Component
  // 高亮tabBar
struct Page12_Tabs_TabBarWithInterface {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Column() {
      Text('当前选中的索引为:' + this.selectedIndex)
      Tabs() {
        TabContent() {
          Text('首页')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_0'), '首页', 0, $r('app.media.ic_tabbar_icon_0_selected')))

        TabContent() {
          Text('我的')
        }
        .tabBar(this.tabBarBuilder($r('app.media.ic_tabbar_icon_3'), '我的', 1, $r('app.media.ic_tabbar_icon_3_selected')))
      }
      .layoutWeight(1)
      .barPosition(BarPosition.End)
      // 增加事件
      .onChange((index) => {
        // 如果是滑动 Tabs 那么久需要用到 onChange 事件
        this.selectedIndex = index
      })
      .onTabBarClick((index) => {
        // 点击 TabBar 的时候触发
        // 保险起见 2 个事件中都保存索引
        this.selectedIndex = index
      })
    }

  }

  @Builder
  tabBarBuilder(img: ResourceStr, text: string, index: number, selectedImg: ResourceStr) {
    Column() {
      Image(this.selectedIndex == index ? selectedImg : img)
        .width(30)
      Text(text)
        .fontColor(this.selectedIndex == index ? '#efc07e' : Color.Black)
    }
  }
}

 参考代码

interface TabParams {
  img: ResourceStr
  selectedImg: ResourceStr
  index: number
  text: string
}

@Entry
@Component
  // 通过对象优化参数
struct Page12_Tabs_TabBarWithInterface {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Column() {
      Text('当前选中的索引为:' + this.selectedIndex)
      Tabs() {
        TabContent() {
          Text('首页')
        }
        .tabBar(this.tabBarBuilder({
          text: '首页',
          index: 0,
          img: $r('app.media.ic_tabbar_icon_0'),
          selectedImg: $r('app.media.ic_tabbar_icon_0_selected')
        }))

        TabContent() {
          Text('我的')
        }
        .tabBar(this.tabBarBuilder({
          text: '我的',
          index: 1,
          img: $r('app.media.ic_tabbar_icon_3'),
          selectedImg: $r('app.media.ic_tabbar_icon_3_selected')
        }))
      }
      .layoutWeight(1)
      .barPosition(BarPosition.End)
      // 增加事件
      .onChange((index) => {
        // 如果是滑动 Tabs 那么久需要用到 onChange 事件
        this.selectedIndex = index
      })
      .onTabBarClick((index) => {
        // 点击 TabBar 的时候触发
        // 保险起见 2 个事件中都保存索引
        this.selectedIndex = index
      })
    }

  }

  @Builder
  tabBarBuilder(param: TabParams) {
    Column() {
      Image(this.selectedIndex == param.index ? param.selectedImg : param.img)
        .width(30)
      Text(param.text)
        .fontColor(this.selectedIndex == param.index ? '#efc07e' : Color.Black)
    }
  }
}

2.5. 案例-小米有品

最后咱们通过一个案例来巩固 Tabs组件的使用,完成小米有品的切换效果

 

需求:

  1. 自定义tabBar-外观
  2. 自定义tabBar-高亮切换
  3. 自定义tabBar-中间的特殊外观

核心步骤:

  1. 自定义tabBar-外观
  2. 自定义tabBar-高亮切换
    1. 定义 Builder,自定义 tabBar外观(先不考虑中间特殊外观)
    2. 定义 interface:默认图片,高亮图片,文本,索引
    3. 定义@State,在事件中保存索引
    4. Builder 定义参数类型为 interface,内部根据参数调整外观机高亮
  1. 自定义tabBar-中间的特殊外观
    1. 额外增加 Builder,中间的 tabBar 使用该 Builder 即可
2.5.1. 基础模版
@Entry
@Component
struct Page13_TabsDemo_Xiaomi {
  build() {
    Tabs() {
      TabContent() {
        Image($r('app.media.ic_xiaomi_content_00'))
      }
      .tabBar('首页')

      TabContent() {
        Image($r('app.media.ic_xiaomi_content_01'))
      }
      .tabBar('分类')

      TabContent() {
        Image($r('app.media.ic_xiaomi_content_02'))
      }
      .tabBar('广告')

      TabContent() {
        Image($r('app.media.ic_xiaomi_content_03'))
      }
      .tabBar('购物车')

      TabContent() {
        Image($r('app.media.ic_xiaomi_content_04'))
      }
      .tabBar('我的')
    }
    .barPosition(BarPosition.End)
  }
}
 2.5.2. 参考答案

interface XMTabParams {
  img: ResourceStr
  selectedImg: ResourceStr
  index: number
  text: string
}

@Entry
@Component
struct Page13_TabsDemo_Xiaomi {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Tabs() {
      TabContent() {
        Image($r("app.media.ic_xiaomi_content_00"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '首页',
          img: $r('app.media.ic_tabbar_icon_0'),
          selectedImg: $r('app.media.ic_tabbar_icon_0_selected'),
          index: 0
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_01"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '分类',
          img: $r('app.media.ic_tabbar_icon_1'),
          selectedImg: $r('app.media.ic_tabbar_icon_1_selected'),
          index: 1
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_02"))
      }
      .tabBar(this.centerTabBarBuilder())

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_03"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '购物车',
          img: $r('app.media.ic_tabbar_icon_2'),
          selectedImg: $r('app.media.ic_tabbar_icon_2_selected'),
          index: 3
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_04"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '我的',
          img: $r('app.media.ic_tabbar_icon_3'),
          selectedImg: $r('app.media.ic_tabbar_icon_3_selected'),
          index: 4
        })
      )
    }
    .barPosition(BarPosition.End)
    .onChange((index: number) => {
      this.selectedIndex = index
    })
    .onTabBarClick((index: number) => {
      this.selectedIndex = index
    })
  }

  // tabBar 的 Builder
  @Builder
  tabBarBuilder(param: XMTabParams) {
    Column({ space: 5 }) {
      Image(this.selectedIndex == param.index ? param.selectedImg : param.img)
        .width(30)
      Text(param.text)
        .fontColor(this.selectedIndex == param.index ? '#efc07e' : Color.Black)
        .fontSize(12)
    }
  }

  // 中间特殊的 tabBar
  @Builder
  centerTabBarBuilder() {
    Image($r('app.media.ic_xiaomi_center_tabBar'))
      .borderRadius(20)
      .height(50)
  }
}

3. 容器组件Badge

可以附加在单个组件上用于信息标记的容器组件,在应用开发中较为常见,比如:

 3.1. 核心用法

 

 Badge是 容器组件,只支持单个子元素

  Badge({count:0,style:{}}){
    // 单个子元素
  }

参数名

参数类型

必填

参数描述

count

number

设置提醒消息数。
说明:
小于等于0时不显示信息标记。

position

BadgePosition|Position10+

设置提示点显示位置。
默认值:BadgePosition.RightTop

maxCount

number

最大消息数,超过最大消息时仅显示maxCount+。
默认值:99
取值范围

style

BadgeStyle

Badge组件可设置样式,支持设置文本颜色、尺寸、圆点颜色和尺寸。

 参考代码:

// 掌握: Badge组件的基本使用
@Entry
@Component
struct Index {
  build() {

    Column({ space: 10 }) {
      // 👍 常用count 和 maxCount
      // 💥 注意style: {}不能省略
      Badge({ count: 101, maxCount: 100, style: {} }) {
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Pink)
      }

      Badge({
        count: 1,
        // 👎不常用 - 了解即可
        style: {
          // 设置字体颜色为绿色
          color: Color.Green,
          // 设置徽标背景色为黄色
          badgeColor: Color.Yellow,
          // 设置字体大小为18
          fontSize: 18,
          // 设置边框颜色
          borderColor: Color.Black
        },
        position: BadgePosition.Right,
      }) {
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Pink)
      }

    }
    .padding(10)

  }
}

 3.2. 案例-小米有品-购物车

 使用刚刚学习的 Badge 组件给上一节的购物车添加商品数

需求:

  1. 购物车 tabBar 添加 Badge 组件
 3.2.1. 基础模版
interface BadgeTabParams {
  img: ResourceStr
  selectedImg: ResourceStr
  index: number
  text: string
}

@Entry
@Component
struct Page15_BadgeDemo_Xiaomi {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Tabs() {
      TabContent() {
        Image($r("app.media.ic_xiaomi_content_00"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '首页',
          img: $r('app.media.ic_tabbar_icon_0'),
          selectedImg: $r('app.media.ic_tabbar_icon_0_selected'),
          index: 0
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_01"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '分类',
          img: $r('app.media.ic_tabbar_icon_1'),
          selectedImg: $r('app.media.ic_tabbar_icon_1_selected'),
          index: 1
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_02"))
      }
      .tabBar(this.centerTabBarBuilder())

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_03"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '购物车',
          img: $r('app.media.ic_tabbar_icon_2'),
          selectedImg: $r('app.media.ic_tabbar_icon_2_selected'),
          index: 3
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_04"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '我的',
          img: $r('app.media.ic_tabbar_icon_3'),
          selectedImg: $r('app.media.ic_tabbar_icon_3_selected'),
          index: 4
        })
      )
    }
    .barPosition(BarPosition.End)
    .onChange((index: number) => {
      this.selectedIndex = index
    })

  }

  // tabBar 的 Builder
  @Builder
  tabBarBuilder(param: BadgeTabParams) {
    Column({ space: 5 }) {
      Image(this.selectedIndex == param.index ? param.selectedImg : param.img)
        .width(30)
      Text(param.text)
        .fontColor(this.selectedIndex == param.index ? '#efc07e' : Color.Black)
        .fontSize(12)
    }
  }

  // 中间特殊的 tabBar
  @Builder
  centerTabBarBuilder() {
    Image($r('app.media.ic_xiaomi_center_tabBar'))
      .borderRadius(20)
      .height(50)

  }
}
 3.2.2. 参考代码

interface BadgeTabParams {
  img: ResourceStr
  selectedImg: ResourceStr
  index: number
  text: string
}

@Entry
@Component
struct Page15_BadgeDemo_Xiaomi {
  // 选中的索引
  @State selectedIndex: number = 0

  build() {
    Tabs() {
      TabContent() {
        Image($r("app.media.ic_xiaomi_content_00"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '首页',
          img: $r('app.media.ic_tabbar_icon_0'),
          selectedImg: $r('app.media.ic_tabbar_icon_0_selected'),
          index: 0
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_01"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '分类',
          img: $r('app.media.ic_tabbar_icon_1'),
          selectedImg: $r('app.media.ic_tabbar_icon_1_selected'),
          index: 1
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_02"))
      }
      .tabBar(this.centerTabBarBuilder())

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_03"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '购物车',
          img: $r('app.media.ic_tabbar_icon_2'),
          selectedImg: $r('app.media.ic_tabbar_icon_2_selected'),
          index: 3
        })
      )

      TabContent() {
        Image($r("app.media.ic_xiaomi_content_04"))
      }
      .tabBar(
        this.tabBarBuilder({
          text: '我的',
          img: $r('app.media.ic_tabbar_icon_3'),
          selectedImg: $r('app.media.ic_tabbar_icon_3_selected'),
          index: 4
        })
      )
    }
    .barPosition(BarPosition.End)
    .onChange((index: number) => {
      this.selectedIndex = index
    })

  }

  // tabBar 的 Builder
  @Builder
  tabBarBuilder(param: BadgeTabParams) {
    if (param.index == 3) {
      Badge({ count: 10, style: {} }) {
        Column({ space: 5 }) {
          Image(this.selectedIndex == param.index ? param.selectedImg : param.img)
            .width(30)
          Text(param.text)
            .fontColor(this.selectedIndex == param.index ? '#efc07e' : Color.Black)
            .fontSize(12)
        }
      }
    } else {
      Column({ space: 5 }) {
        Image(this.selectedIndex == param.index ? param.selectedImg : param.img)
          .width(30)
        Text(param.text)
          .fontColor(this.selectedIndex == param.index ? '#efc07e' : Color.Black)
          .fontSize(12)
      }
    }
  }

  // 中间特殊的 tabBar
  @Builder
  centerTabBarBuilder() {
    Image($r('app.media.ic_xiaomi_center_tabBar'))
      .borderRadius(20)
      .height(50)

  }
}

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值