Scroll、Tabs、Badge
今日核心:
● 容器组件:Scroll、Tabs、Badge
1. 容器组件 Scroll
可滚动的容器组件,当子组件的布局尺寸超过Scroll的尺寸时,内容可以滚动。
当页面内容由多个区域组成,并且可以滚动时,推荐使用 Scroll,比如:
- 小米有品:页面滚动,点击顶部区域,返回顶部
- 京东:页面滚动,点击右下角小火箭,返回顶部
等等…
1.1 核心用法
先来看看 Scroll 的最核心用法,让内容滚动
核心用法:
- Scroll 设置尺寸
- 设置 子组件(只支持一个子组件)
- 设置滚动:
a. 竖向滚动:子组件的高度超出 Scroll
b. 横向滚动:子组件的宽度超出 Scroll,>scrollable改为横向滚动- 根据需求调整属性
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 阴影 |
试一试:
- 测试横向和竖向滚动
- 关闭滚动条
基础模版
@Entry
@Component
struct Page01_Scroll {
build() {
Column({space:10}){
Text('竖向滚动')
.fontSize(20)
// Scroll 容器尺寸固定
// 内容横向超出 Scroll 即可滚动
Scroll(){
}
.width('100%')
.height(200)
.border({width:1,color:Color.Orange})
Divider()
Text('横向滚动')
.fontSize(20)
// Scroll 容器尺寸固定
// 内容横向超出 Scroll 即可滚动
Scroll(){
}
// 横向滚动
.width('100%')
.height(200)
.border({width:1,color:Color.Orange})
}
.width('100%')
.height('100%')
}
}
参考代码:
@Entry
@Component
struct Page01_Scroll {
build() {
Column({ space: 10 }) {
Text('竖向滚动')
.fontSize(20)
// Scroll 容器尺寸固定
// scrollable 设置横向
// 内容横向超出 Scroll 即可
Scroll() {
// 设置内容
Column() {
}
.height(1000)
.width('100%')
// 线型渐变
.linearGradient({
colors: [['#0094ff', 0], [Color.Orange, 1]],
})
}
.scrollBarColor(Color.Pink)
.scrollBarWidth(50)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
.width('100%')
.height(200)
.border({ width: 1, color: Color.Orange })
Divider()
Text('横向滚动')
.fontSize(20)
// Scroll 容器尺寸固定
// scrollable 设置横向
// 内容横向超出 Scroll 即可
Scroll() {
Row() {
}
.height('100%')
.width(1000)
.linearGradient({
angle: 90,
colors: [['#0094ff', 0], [Color.Orange, 1]],
})
}
// 横向滚动
.scrollable(ScrollDirection.Horizontal)
.width('100%')
.height(200)
.border({ width: 1, color: Color.Orange })
.edgeEffect(EdgeEffect.Spring)
}
.width('100%')
.height('100%')
}
}
1.2 案例-小米有品
接下来结合 Scroll 来实现小米有品首页的滚动效果
需求:
- 使用 Scorll 实现页面滚动滚动
- 关闭滚动条
- 滚动屏幕边缘 弹簧效果
基础模版:
@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 的控制器来实现
比如:
- 页面滚动超过一定距离,显示返回顶部(火箭),反之隐藏–获取滚动距离
- 点击返回顶部(火箭),返回顶部–代码控制滚动
核心步骤:
- 实例化 Scroller的 控制器
- 绑定给 Scroll
- 调用 控制器的方法 控制滚动 以及 获取滚动距离
@Entry
@Component
struct Page03_Scroller {
// 1. 创建 Scroller 控制器
scroller: Scroller = new Scroller()
build() {
Column() {
// 2. 设置给Scroll
Scroll(this.scroller) {
// 内容略
}
}
}
}
这里用到了 2 个方法:
- scrollEdge:滚动到边缘
- currentOffset:返回当前的偏移量
1.3.1 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.2 currentOffset 方法
this.scroller.currentOffset().xOffset // x 轴滚动距离
this.scroller.currentOffset().yOffset // y 轴滚动距离
参数名 | 描述 |
---|---|
{xOffset: number, yOffset: number} | xOffset: 水平滑动偏移; yOffset: 竖直滑动偏移。说明:返回值单位为vp。 |
基础模版
@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%')
}
}
参考代码:
@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 案例-京东
接下来咱们利用刚刚学习的内容,来完成京东的返回顶部效果
需求:
- 布局效果(结合图片完成:>ic_jd_scroll_01-03、ic_jd_scroll_tab)
a. 内容区域滚动
b. 底部区域
c. 火箭始终在右下角- 点击火箭(ic_jd_rocket)返回顶部
- 火箭显示效果切换:
a. 默认隐藏
b. 滚动距离超过 400 显示
c. 滚动距离小于 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((x, y) => {
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 已经可以实现切换,接下来咱们来看看如何通过属性控制他
- 垂直导航
- 导航位置
- 禁用滑动切换
通过 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 属性即可调整 固定导航栏 或 滚动导航栏
@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-自定义外观
Tabs() {
TabContent() {
// 内容略
}
.tabBar(this.tabBarBuilder())
}
@Builder
tabBarBuilder() {
// 自定义的Tabbar结构
}
通过自定义 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 之后,高亮的切换效果就没有了,需要自行实现,咱们分两步走:
- 明确什么时候 tab 进行了切换
- 更改高亮效果
名称 | 功能描述 |
---|---|
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-高亮切换
结合刚刚学习的事件,来实现高亮的切换效果
核心思路:
- 用状态变量保存,onChange,ontabBarClick中获取到的索引值
- 每个 TabBar 传递索引值,0,1…(调整 Builder 的参数)
- 在tabBar内部比较 标记==this.index?高亮:不高亮
a. 默认文字颜色和高亮文字颜色(三元表达式)
b. 默认图片和高亮图片(调整 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 个,需要严格按照顺序传递,咱们来优化一下
需求:
- 使用对象作为 tabBarBuilder 的参数
- 对象中设置 默认图片、高亮图片、文本、索引值
核心步骤:- 定义 interface
- 调整 tabBarBuilder
a. 参数:形参,实参
b. 内部逻辑:改为通过对象获取属性
@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组件的使用,完成小米有品的切换效果
需求:
- 自定义tabBar-外观
- 自定义tabBar-高亮切换
- 自定义tabBar-中间的特殊外观
核心步骤: - 自定义tabBar-外观
- 自定义tabBar-高亮切换
a. 定义 Builder,自定义 tabBar外观(先不考虑中间特殊外观)
b. 定义 interface:默认图片,高亮图片,文本,索引
c. 定义@State,在事件中保存索引
d. Builder 定义参数类型为 interface,内部根据参数调整外观机高亮 - 自定义tabBar-中间的特殊外观
a. 额外增加 Builder,中间的 tabBar 使用该 Builder 即可
基础模版:
@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)
}
}
参考答案:
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 | BadgePositionPosition10+ | 否 | 设置提示点显示位置。默认值:BadgePosition.RightTop |
maxCount | number | 否 | 最大消息数,超过最大消息时仅显示maxCount+。默认值:99取值范围 |
style | BadgeStyle | 是 | Badge组件可设置样式,支持设置文本颜色、尺寸、圆点颜色和尺寸。 |
参考代码:
@Entry
@Component
struct Page14_Badge {
build() {
Column() {
Text('Badge 组件')
.fontSize(50)
.fontWeight(FontWeight.Bold)
// 基本用法
Badge({ count: 10, style: {} }) {
Text('信息')
.border({ width: .5, })
.borderRadius(10)
.padding(10)
}
// 调整位置
Badge({ count: 10, position: BadgePosition.Left, style: {} }) {
Text('信息')
.border({ width: .5, })
.borderRadius(10)
.padding(10)
}
// 3. 最大个数
Badge({ count: 12, maxCount: 10, position: BadgePosition.Right, style: {} }) {
Text('感觉自己萌萌哒')
.border({ width: .5, })
.borderRadius(10)
.padding(10)
}
// 4. 调整样式
Badge({
count: 12, style: {
fontSize: 20,
color: Color.Orange,
badgeColor: Color.Black,
borderColor: Color.Black,
badgeSize: 30
}
}) {
Text('感觉自己萌萌哒')
.border({ width: .5, })
.borderRadius(10)
.padding(10)
}
}
.width('100%')
.height('100%')
}
}
3.2 案例-小米有品-购物车
使用刚刚学习的 Badge 组件给上一节的购物车添加商品数
需求:
- 购物车 tabBar 添加 Badge 组件
基础模版:
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
})
.onTabBarClick((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)
}
}
参考代码:
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
})
.onTabBarClick((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)
}
}