08_案例三-自定义tabBar
案例效果
在此案例中,用到的知识点如下
- 自定义组件
- Vant 组件库
- MobX 数据共享
- 组件样式隔离
- 组件数据监听器
- 组件的 behaviors
- Vant 样式覆盖
实现步骤:
自定义 tabBar 分为 3 大步骤,分别是:
- 配置信息
- 添加 tabBar 代码文件
- 编写 tabBar 代码
详细步骤,可以参考小程序官方给出的文档
准备工作
在这里我们就不新建一个项目了,就是用本系列博客案例一和二中的小程序。
绘制联系我们(contact)页面,并且达到点击按钮,数字增加的效果
代码如下
//contact.wxml部分
<view class="oper">{{numA}} + {{numB}} = {{numA + numB}}</view>
<view class="view0">
<view class="view1">
<button size="mini" type="primary" bindtap="changeNumA" data-num='{{1}}'>A+</button>
<button size="mini" type="warn" bindtap="changeNumA" data-num='{{-1}}'>A-</button>
</view>
<view class="view1">
<button size="mini" type="primary" bindtap="changeNumB" data-num='{{1}}'>B+</button>
<button size="mini" type="warn" bindtap="changeNumB" data-num='{{-1}}'>B-</button>
</view>
</view>
// contact.wxss
.oper {
font-size: 40rpx;
height: 100rpx;
border: 2rpx solid #efefef;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.view0 {
margin-top: 100rpx;
}
.view1 {
display: flex;
align-content: space-around;
margin-top: 20rpx;
margin-left: 250rpx;
margin-right: 250rpx;
}
// contact.js
Page({
/**
* 页面的初始数据
*/
data: {
numA: 0,
numB: 0,
sum: 0
},
changeNumA(e) {
this.setData({ numA: e.target.dataset.num + this.data.numA })
},
changeNumB(e) {
this.setData({ numB: e.target.dataset.num + this.data.numB })
},
onLoad: function (options) {
},
onReady: function () {
},
onShow: function () {
},
onHide: function () {
},
onUnload: function () {
},
onPullDownRefresh: function () {
},
onReachBottom: function () {
},
onShareAppMessage: function () {
}
})
效果
配置
1. 配置信息
- 在 app.json 中的 tabBar 项指定 custom 字段,同时其余 tabBar 相关配置也补充完整。
- 所有 tab 页的 json 里需声明 usingComponents 项,也可以在 app.json 全局开启。
2. 添加 tabBar 代码文件
在代码根目录下添加入口文件:
custom-tab-bar/index.js
custom-tab-bar/index.json
custom-tab-bar/index.wxml
custom-tab-bar/index.wxss
注意,文件路径名字必须与上述一样,一旦我们自定义了tabbar,那小程序就会去custom-tab-bar文件夹下寻找对应的index文件
可以看到小程序已经自动识别,并将其作为tabbar
3. 渲染tabbar
自定义tabbar的话,建议使用vant组件,vant安装 和 vant自定义tabbar
注册vant组件
修改 app.json 添加如下代码
"usingComponents": {
"van-tabbar": "@vant/weapp/tabbar/index",
"van-tabbar-item": "@vant/weapp/tabbar-item/index"
}
复值并修改代码
直接在custom-tab-bar/index.wxml中粘贴如下代码,这段代码复制自官网
<van-tabbar active="{{ active }}" bind:change="onChange">
<van-tabbar-item icon="home-o">标签</van-tabbar-item>
<van-tabbar-item icon="search">标签</van-tabbar-item>
<van-tabbar-item icon="friends-o">标签</van-tabbar-item>
<van-tabbar-item icon="setting-o">标签</van-tabbar-item>
</van-tabbar>
在custom-tab-bar/index.js增加如下代码
data: {
active: 0
},
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
this.setData({ active: event.detail });
}
}
效果如下:
初步的自定义渲染完成,可以看到,因为我们在custom-tab-bar/index.wxml中自定义了四个组件,下方展示了四个小图标。js文件中的data块中定义的active变量表示当前展示的tabbar页面的id,当我们点击对应的tabbar图标,会触发onchange函数,修改这个active的值
目前这些图标是自带的,如果我们想像之前一样自定义选中和未选中的图标,还有tabbar文本信息,该如何做,继续看vant官方文档
我们复制粘贴,并对custom-tab-bar/index.wxml
做出改造,代码如下
<van-tabbar active="{{ active }}" bind:change="onChange">
<van-tabbar-item info="3">
<image slot="icon" src="/images/tabs/home.png" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="/images/tabs/home-active.png" mode="aspectFit" style="width: 30px; height: 18px;" />
首页
</van-tabbar-item>
<van-tabbar-item info="3">
<image slot="icon" src="/images/tabs/message.png" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="/images/tabs/message-active.png" mode="aspectFit" style="width: 30px; height: 18px;" />
消息
</van-tabbar-item>
<van-tabbar-item info="3">
<image slot="icon" src="/images/tabs/contact.png" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="/images/tabs/contact-active.png" mode="aspectFit" style="width: 30px; height: 18px;" />
联系我们
</van-tabbar-item>
</van-tabbar>
可以看到图标以及文本已经修改过来了,每个图标上方的小红圈数字就是上述代码中info指定的数字,如果修改info的值,则这个小红圈里的值也会跟着改变
继续改造,我们可以把之前的tabbar数组放到组件的data块中,然后用wx:for来循环渲染tabbar标签,就更完美了
// custom-tab-bar/index.js
data: {
active: 0,
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/images/tabs/home.png",
"selectedIconPath": "/images/tabs/home-active.png"
},
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png"
},
{
"pagePath": "pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
// custom-tab-bar/index.wxml
<van-tabbar active="{{ active }}" bind:change="onChange">
<van-tabbar-item wx:for="{{list}}" wx:key="index">
<image slot="icon" src="{{item.iconPath}}" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="{{item.selectedIconPath}}" mode="aspectFit" style="width: 30px; height: 18px;" />
{{item.text}}
</van-tabbar-item>
</van-tabbar>
重新编译
可以看到,三个tabbar标签已经展示出来了,不过这个图标太小了,可以直接在wxml文件中修改尺寸,
继续美化
仔细看就会发现这个小红圆标超出了tabbar的范围,部分遮盖了正文页面,并且tabbr图标与文字之间的间隙有点大,所以我们重置这个间隙,将其置为0,则小红圆标就会回到tabbar区域
在控制台进行前端调试,会发现他有个margin-bottom属性,将其取消和选中,就会发现tabbar的明显变化,这个值就是我们要修改的。这个是不容易找到的,读者要细细查找。
在custom-tab-bar/index.wxss文件中,重置这个变量
// van-tabbar-item是tabbar图标父节点的class值,在这里是指定覆盖的css变量值的作用域
.van-tabbar-item {
--tabbar-item-margin-bottom: 0;
}
重新编译会发现tabbar标签还是没有变化。我们查看vant官网文档
我们修改custom-tab-bar/index.js文件
Component({
options: {
styleIsolation: 'shared',
},
});
重新检查就会发现中间已经没有间隙了。
继续美化,那个小红圆标不是每个tabbar组件都需要,一般来说只有消息那个组件才需要,接下来该如何做?
我们给custom-tab-bar/index.js文件中的data数据块里的message增加一个属性,info,并给定初始值为2,在前端展示时做出判断,就可以按需绘制小红圆标
// index.js
{
"pagePath": "pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png",
info: 2
},
// index.wxml
<van-tabbar active="{{ active }}" bind:change="onChange">
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info?item.info:''}}">
<image slot="icon" src="{{item.iconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
<image slot="icon-active" src="{{item.selectedIconPath}}" mode="aspectFit" style="width: 25px; height: 25px;" />
{{item.text}}
</van-tabbar-item>
</van-tabbar>
效果:
2. 将contact中的数字映射到小红圆标中
之前的操作,把小红圆标中的数字写死了,而我们最终的目的是实现contact页面的加减按钮操作修改sum值,并且实时修改到tabbar上。
tabbar是我们自定义的组件,不能跟外部组件是隔离的,不能拿到外界的数据,外界也不能随意更改自定义组件的数据,再者,我们是在contact页面更改这个小红圆标的数据,那要是这个页面没有加载,我岂不是获取不到数据,这个数据无论是放在自定义组件这一侧还是放在contact页面这一侧都不好。
我们可以使用MobX来做一个全局的数据共享。具体知识请看博客
还有一点,现在我这个程序默认是打开主页,目前自定义tabbar还不能完成页面跳转,这个后面在解决,为了测试点击contact页面的按钮,修改message的小红圆标,我们需要修改编译模式,让默认打开页是contact页面
第一步:安装 MobX 相关的包
在项目中运行如下的命令,安装 MobX 相关的包:
npm install --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1
注意:MobX 相关的包安装完毕之后,记得删除 miniprogram_npm 目录后,重新构建 npm。
2. 创建 MobX 的 Store 实例
在根目录下创建store文件夹,用来存放所有的store实例
编辑store.js
import { action, observable } from 'mobx-miniprogram'
export const store = observable({
numA: 0,
numB: 0,
get sum() {
return this.numA + this.numB
},
updateNumA: action(function (step) {
this.numA += step
}),
updateNumB: action(function (step) {
this.numB += step
}),
})
3. 将 Store 中的成员绑定到页面中
修改contcat.js文件
import { createStoreBindings } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Page({
data: {
},
changeNumA(e) {
this.updateNumA( e.target.dataset.num )
},
changeNumB(e) {
this.updateNumB( e.target.dataset.num )
},
onLoad: function (options) {
this.stroreBindings = createStoreBindings(this, {
store,
fields: ['numA', 'numB', 'sum'],//需要导入哪些数据
actions: ['updateNumA', 'updateNumB']//需要导入哪些方法
})
},
onUnload: function () {
this.storeBindings.destroyStoreBindings()
},
})
重点是我写出来的这个函数需要改造一番,经过测试,原本的功能点击+—按钮可以实现计算表达式的改变。
接下来的才是重点:修改tabbar组件,从共享数据中动态获取sum值,来修改小红圆标
改造自定义tabbar的js文件,我只列出修改的代码块,剩余的地方都是没有发生改动的。
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../store/store'
Component({
behaviors: [storeBindingsBehavior],
storeBindings: {
store,
fields: {
info: 'sum' //在导入共享数据变量时,我们把sum去了别名为info,跟当前页面所需值同名
},
},
data: {
active: 0,
"list": [
{
"pagePath": "/pages/home/home",
"text": "首页",
"iconPath": "/images/tabs/home.png",
"selectedIconPath": "/images/tabs/home-active.png"
},
{
"pagePath": "/pages/message/message",
"text": "消息",
"iconPath": "/images/tabs/message.png",
"selectedIconPath": "/images/tabs/message-active.png",
info: 0 //此处我们之前测试的时候写死了写成了2,现在修改默认值为0
},
{
"pagePath": "/pages/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
// 添加变量监控,一旦A或者B改变了导致sum值发生了变化,就会触发内部方法,修改list变量中的info属性
observers:{
'info':function(info){
this.setData({'list[1].info':info})
}
}
})
改造完成
tabbar页面切换
tabbar自定义组件中定义了一个onchange方法,点击自定义组件的时候就会执行onchange方法,我们在onchange方法里执行wx.switchTab函数完成页面跳转
修改custom-tab-bar/index.js的部分代码如下:
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
this.setData({ active: event.detail });
wx.switchTab({
// 直接依据detail的值,从list数组中获取路径值
url: this.data.list[event.detail].pagePath,
})
}
},
需要注意的是,使用wx.switchTab跳转的时候必须使用项目根目录下的绝对路径,因此list中的路径必须都在最前方加上/
。
经过测试发现页面确实发生了跳转,但是,tabbar的被选中图表一直是首页。
这是因为跳转的时候页面发生了刷新,自定义组件又被渲染了一次,这样的话active的值就一直是默认值0,也就是说这个主页的tabbar图标会一直被选中。一个很好的解决办法是将active放到共享数据中,每次页面跳转都重新从共享数据中获取最新值。
因此有两个文件需要修改,一个store.js这个全局共享数据文件,一个是自定义tabbar的index.js组件,同样的我只会展示要修改的部分代码,为展示的不用修改
//store.js
export const store = observable({
active: 0, //选中图标的id值
// 操作active的方法
updateActive: action(function (index) {
this.active = index
}),
})
// index.js
Component({
options: {
styleIsolation: 'shared'
},
behaviors: [storeBindingsBehavior],
storeBindings: {
store,
fields: {
info: 'sum',
active:'active' //导入active
},
actions:{
updateActive:'updateActive' //导入修改active的方法
}
},
data: {
active:0, //这一行要去掉,因为数据已经不是自定义了,而是从全局共享中取
"list": [
{
"pagePath": "/pages/home/home", //每个路径的开头都要价格斜杠
},
{
"pagePath": "/pages/message/message",
},
{
"pagePath": "/pages/contact/contact",
}
]
},
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
// 调用修改active的方法
this.updateActive(event.detail);
wx.switchTab({
url: this.data.list[event.detail].pagePath,
})
}
},
我经过测试已经完成了页面跳转时的正确选中状态
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库