08_案例三-自定义tabBar

案例效果

在此案例中,用到的知识点如下

  • 自定义组件
  • Vant 组件库
  • MobX 数据共享
  • 组件样式隔离
  • 组件数据监听器
  • 组件的 behaviors
  • Vant 样式覆盖

实现步骤:

自定义 tabBar 分为 3 大步骤,分别是:

  1. 配置信息
  2. 添加 tabBar 代码文件
  3. 编写 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,
      })
    }
  },

我经过测试已经完成了页面跳转时的正确选中状态

posted @   yaowy  阅读(598)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示