微信小程序自定义tabBar

概括

  • 注意事项
  • 项目结构
  • 代码
  • 适配低版本

参考文档:

小程序官方的tabBar文档
小程序官方的Component文档
小程序app.json的配置

注意事项

  1. 自定义tabBar 基础库 2.5.0 开始支持
  2. 自定义tabBar的组件一定要叫 custom-tab-bar 并且一定要与app.js同级
  3. 在 app.json 中的 tabBar 项指定 custom 字段,同时其余 tabBar 相关配置也补充完整。
  4. 所有 tab 页的 json 里需声明usingComponents 项,也可以在 app.json 全局开启。
// app.json 记得删掉注释

  "usingComponents": {
    "nav": "/components/nav_bar/nav_bar" // 用与做低版本适配的 自定义tabBar组件
  },
  "tabBar": {
    "custom": true, // 打开tabBar的自定义功能
    "color": "#212121",
    ...

项目结构

  • 代替原生tabBar的 custom-tab-bar 是不是一定要这样放?
    是的,一定要放在与app.js同级的项目结构, 而且一定要叫 custom-tab-bar
    微信小程序自定义tabBar 项目结构

先码为敬

custom-tab-bar

  1. wxml
<!--components/custom-tab-bar/index.wxml-->
<view class='my-bar ak-flexB {{isIpx ? "hackIPX" : ""}}'>
  <view wx:for="{{list}}" wx:key="index" class='my-bar__item ak-flexC' data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab" data-jump_type='{{item.jumpType}}'>
    <view class='my-bar__item-text ak-flex-columnC {{selected == index ? "my-bar__item-active" : ""}}'>
      <image class='my-bar__btn-img animated' mode='widthFix' src='{{selected === index ? item.selectedIconPath : item.iconPath}}'></image>
      {{item.text}}
      <view hidden="{{item.tagNum <= 0}}" class='my-bar__item-tag ak-flexC'>
        {{item.tagNum}}
      </view>
    </view>
  </view>
</view>
  1. js
// components/custom-tab-bar/index.js
let app = getApp();
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    now: {
      type: String,
      value: 'index'
    },
    cartNum: {
      type: Number,
      value: 0,
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    isIpx: app.globalData.isIpx,  // 用于适配全面屏的底部高度(iPhone X* 的底部杠杠)
    selected: 0, // 当前选中的项
    color: "#333", // 未选中的字体的颜色
    selectedColor: "#d01716", // 选中时的字体颜色
    list: [{
      pagePath: "/pages/index/index", // 跳转路径, 【switchTab的跳转一定要在app.json中配置】
      iconPath: "/images/icon_index.png",
      selectedIconPath: "/images/icon_index_act.png",
      jumpType: "switchTab", // 跳转的类型
      tagNum: 0,
      text: "首页"
    }, {
      pagePath: "/pages/classificationII/classificationII",
      iconPath: "/images/icon_classify.png",
      selectedIconPath: "/images/icon_classify_act.png",
      jumpType: "switchTab",
      tagNum: 0,
      text: "分类"
    }, {
      pagePath: "/pages/shoppingCart/shoppingCart",
      iconPath: "/images/icon_cart.png",
      selectedIconPath: "/images/icon_cart_act.png",
      jumpType: "navigateTo",
      tagNum: 0,
      text: "购物车"
    }, {
      pagePath: "/pages/center2/center2",
      iconPath: "/images/icon_my.png",
      selectedIconPath: "/images/icon_my_act.png",
      jumpType: "switchTab",
      tagNum: 0,
      text: "我的"
    }]
  },
  /**
   * 组件的方法列表
   */
  methods: {
    switchTab(e) {
      const data = e.currentTarget.dataset
      const url = data.path
      const jumpType = data.jump_type;
      wx[jumpType]({
        url
      })
      this.setData({
        selected: data.index
      })
    },
  }
})
  1. wxss
/* components/custom-tab-bar/index.wxss */
/*      弹性盒居中            */
.animated {
  -webkit-animation-duration: .7s;
  animation-duration: .7s;
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
}

.animated.infinite {
  -webkit-animation-iteration-count: infinite;
  animation-iteration-count: infinite;
}
@keyframes bounceIn {
  from,  20%,  40%,  60%,  80%,
  to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  }
  0% {
    opacity: 0;
    -webkit-transform: scale3d(0.3, 0.3, 0.3);
    transform: scale3d(0.3, 0.3, 0.3);
  }
  20% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1);
    transform: scale3d(1.1, 1.1, 1.1);
  }
  40% {
    -webkit-transform: scale3d(0.9, 0.9, 0.9);
    transform: scale3d(0.9, 0.9, 0.9);
  }
  60% {
    opacity: 1;
    -webkit-transform: scale3d(1.03, 1.03, 1.03);
    transform: scale3d(1.03, 1.03, 1.03);
  }
  80% {
    -webkit-transform: scale3d(0.97, 0.97, 0.97);
    transform: scale3d(0.97, 0.97, 0.97);
  }

  to {
    opacity: 1;
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

.bounceIn {
  -webkit-animation-duration: 0.75s;
  animation-duration: 0.75s;
  -webkit-animation-name: bounceIn;
  animation-name: bounceIn;
  animation-delay: 0.26s;
}

.ak-flexC {
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-justify-content: space-around;
    justify-content: space-around;
    -moz-box-pack: space-around;
    -webkit--moz-box-pack: space-around;
    box-pack: space-around;
    align-items: center;
    -webkit-align-items: center;
    box-align: center;
    -moz-box-align: center;
    -webkit-box-align: center;
}

/*      弹性盒居两边            */
.ak-flexB {
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-justify-content: space-between;
    justify-content: space-between;
    -moz-box-pack: space-between;
    -webkit--moz-box-pack: space-between;
    box-pack: space-between;
    align-items: center;
    -webkit-align-items: center;
    box-align: center;
    -moz-box-align: center;
    -webkit-box-align: center;
}

/*              弹性盒纵向排列(居中)              */
.ak-flex-columnC {
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-justify-content: space-around;
    justify-content: space-around;
    -moz-box-pack: space-around;
    -webkit--moz-box-pack: space-around;
    box-pack: space-around;
    align-items: center;
    -webkit-align-items: center;
    box-align: center;
    -moz-box-align: center;
    -webkit-box-align: center;
    -webkit-box-orient: vertical;
    -webkit-box-direction: normal;
    -moz-box-orient: vertical;
    -moz-box-direction: normal;
    flex-direction: column;
    -webkit-flex-direction: column;
}
view{
  box-sizing: border-box;
}
.my-bar{
  position: fixed;
  bottom: 0;
  left: 0;
  z-index: 9;
  width: 100%;
  background-color: rgba(254, 254, 254, .96);
  box-shadow: 0 0 16px rgba(155, 155, 155, .5);
}
.my-bar__item{
  flex: 1;
  height: 98rpx;
  padding-top: 10rpx;
}
.my-bar__item-text{
  height: 100%;
  text-align: center;
  width: 100%;
  color: #333;
  position: relative;
}
.my-bar__item-text2:last-child{
  height: 100%;
  text-align: center;
  width: 100%;
  color: #333;
}

.my-bar__item-text, .my-bar__item-text2{
  font-size: 22rpx;
}
.my-bar__item-tag{
  position: absolute;
  top: 0;
  right: 62rpx;
  width: 26rpx;
  height: 26rpx;
  background-color: #d01716;
  color: #fff;
  border-radius: 50%;
  font-size: 20rpx;
}
.iconfont{
  font-size: 46rpx;
}

.my-bar__item-active{
  color: #d01716 !important;
}


.hackIPX{
  box-sizing: content-box;
  padding-bottom: 68rpx;
}

.my-bar__btn-img{
  width: 50rpx;
  height: 50rpx;
}
.my-bar__btn-img-act{
  width: 60rpx;
  height: 60rpx;
}

.hackIPX{
  box-sizing: content-box;
  padding-bottom: 48rpx;
  padding-top: 20rpx;
}
  1. app.json

  "usingComponents": {
    "nav": "/components/nav_bar/nav_bar"
  },
  "tabBar": {
    "custom": true,
    "color": "#212121",
    "selectedColor": "#d01716",
    "backgroundColor": "#fefefe",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "/images/icon_index.png",
        "selectedIconPath": "/images/icon_index_act.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/classificationII/classificationII",
        "iconPath": "/images/icon_classify.png",
        "selectedIconPath": "/images/icon_classify_act.png",
        "text": "分类"
      },
      {
        "pagePath": "pages/center2/center2",
        "iconPath": "/images/icon_my.png",
        "selectedIconPath": "/images/icon_my_act.png",
        "text": "我的"
      }
    ]
  },
  1. 在用到tabBar的页面.js
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {
    let _this = this;
    if (!app.globalData.accessToken){
      // Tips: 1、没有有登录状态: 只设置tabbar
      console.log('没有有登录状态: 只设置tabbar')
      if (typeof this.getTabBar === 'function' &&
        this.getTabBar()) {
        this.getTabBar().setData({
          ['selected']: 0
        })
      }
    }else{
      // Tips: 2、有登录状态: 拿购物车 用户消息 优惠券数据
      console.log('有登录状态: 拿购物车 用户消息 优惠券数据')
      app.myRequest('cartNum', {}, 'get',
        function (res) {
          if (typeof _this.getTabBar === 'function' &&
            _this.getTabBar()) {
            _this.getTabBar().setData({
              ['selected']: 0,
              ['list[2].tagNum']: res.data
            })
          }
          _this.setData({
            cartNums: res.data
          })
        }, 'top');
      this.showCoupons();
    }
  },


// 其实核心代码 其他的代码跟我的业务逻辑挂钩的
//if (typeof this.getTabBar === 'function' &&
//  this.getTabBar()) {
//  this.getTabBar().setData({
//    ['selected']: 0
//  })
//}

OK 以上的这些玩意儿就够你玩转小程序的自定义tabBar了
但是我们要考虑到有些用户死活不升级的情况
(别问我为什么,在遍地都是iOS12.x 的时候,我的用户还有iOS8.x 的)
就是微信版本低, 小程序的基础库版本也低,不支持展示小程序的自定义tabBar;
此时如果我们不做兼容的话 用户将看不到底部栏

适配低版本

  • 注意, 我们这里用的是自定义组件来代替小程序的自定义tabBar
    从项目结构上来说是用/components/tab_bar 来代替 /custom-tab-bar
    由于用到了自定义组件 而支自定义组件是从1.6.3 的基础版本开始支持的
    如果需要稳定的话, 推荐线上最低基础库需要支持到 2.1.0

关键点有3个

  1. 自己的tabBar组件
  2. 隐藏官方的tabBar
  3. 控制自己的tabBar组建的显示和隐藏

1. 自己的tanBar组件

a. /components/tab_bar/tab_bar.js

// components/table_bar/table_bar.js
let app = getApp();
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    now: {
      type: String,
      value: 'index'
    },
    cartNum: {
      type: Number,
      value: 0,
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    isIpx: app.globalData.isIpx,
  },
  /**
   * 组件的方法列表
   */
  methods: {
    _tableJump: function(e) {
      let jumpType = e.currentTarget.dataset.jump;
      if (jumpType == this.properties.now) {
        return;
      } else {
        if (jumpType == 'index') {
          wx.switchTab({
            url: '/pages/index/index'
          })
        } else if (jumpType == 'my') {
          wx.switchTab({
            url: '/pages/center2/center2'
          })
        } else if (jumpType == 'classify') {
          wx.switchTab({
            url: '/pages/classificationII/classificationII'
          })
        } else if (jumpType == 'cart') {
          wx.navigateTo({
            url: '/pages/shoppingCart/shoppingCart'
          })
        } else {
          wx.reLaunch({
            url: '/pages/find/find'
          })
        }
      }
    },
  }
})

b. /components/tab_bar/tab_bar.wxml

<!--components/tab_bar/tab_bar.wxml-->

<view class='my-bar ak-flexB {{isIpx ? "hackIPX" : ""}}'>
  <view class='my-bar__item ak-flexC' bindtap='_tableJump' data-jump='index'>
    <view class='my-bar__item-text ak-flex-columnC {{now == "index" ? "my-bar__item-active" : ""}}'>
      <image src='/images/icon_index.png' class='my-bar__btn-img animated' mode='widthFix' hidden="{{now == 'index'}}"></image>
      <image src='/images/icon_index_act.png' class='my-bar__btn-img-act animated bounceIn' mode='widthFix' hidden="{{now != 'index'}}"></image>
      首页
    </view>
  </view>

  <view class='my-bar__item ak-flexC' bindtap='_tableJump' data-jump='classify'>
    <view class='my-bar__item-text ak-flex-columnC {{now == "classify" ? "my-bar__item-active" : ""}}'>
      <image src='/images/icon_classify.png' class='my-bar__btn-img animated' mode='widthFix' hidden="{{now == 'classify'}}"></image>
      <image src='/images/icon_classify_act.png' class='my-bar__btn-img-act animated bounceIn' mode='widthFix' hidden="{{now != 'classify'}}"></image>
      分类
    </view>
  </view>

  <view class='my-bar__item ak-flexC' bindtap='_tableJump' data-jump='cart'>
    <view class='my-bar__item-text ak-flex-columnC {{now == "cart" ? "my-bar__item-active" : ""}}'>
      <image src='/images/icon_cart.png' style='width: 56rpx; margin-top: -4rpx;' class='my-bar__btn-img animated' mode='widthFix' hidden="{{now == 'cart'}}"></image>
      <image src='/images/icon_cart_act.png' class='my-bar__btn-img-act animated bounceIn' mode='widthFix' hidden="{{now != 'cart'}}"></image>
      购物车
      <view hidden="{{cartNum <= 0}}" class='my-bar__item-tag ak-flexC'>{{cartNum}}</view>
    </view>
  </view>

  <view class='my-bar__item ak-flexC' bindtap='_tableJump' data-jump='my'>
    <view class='my-bar__item-text2 ak-flex-columnC {{now == "my" ? "my-bar__item-active" : ""}}'>
      <image src='/images/icon_my.png' class='my-bar__btn-img animated' mode='widthFix' hidden="{{now == 'my'}}"></image>
      <image src='/images/icon_my_act.png' class='my-bar__btn-img-act animated bounceIn' mode='widthFix' hidden="{{now != 'my'}}"></image>
      我的
    </view>
  </view>
</view>

c. 在使用的页面:

pages/index/index.wxml
<!-- pages/index/index.wxml -->
<tab-bar wx:if="{{useMyTB}}" now="index" cart-num="{{cartNums}}"></tab-bar>

pages/index/index.js :
Page({
  data: {
    useMyTB: app.globalData.useMyTB,
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {
    let _this = this;
    if (!app.globalData.accessToken){
      // Tips: 1、没有有登录状态: 只设置tabbar
      console.log('没有有登录状态: 只设置tabbar')
      if (typeof this.getTabBar === 'function' &&
        this.getTabBar()) {
        this.getTabBar().setData({
          ['selected']: 0
        })
      }
    }else{
      // Tips: 2、有登录状态: 拿购物车 用户消息 优惠券数据
      console.log('有登录状态: 拿购物车 用户消息 优惠券数据')
      
      app.myRequest('cartNum', {}, 'get',
        function (res) {
          if (typeof _this.getTabBar === 'function' &&
            _this.getTabBar()) {
            _this.getTabBar().setData({
              ['selected']: 0,
              ['list[2].tagNum']: res.data
            })
          }
          _this.setData({
            cartNums: res.data
          })
        }, 'top');

      app.myRequest('centerInfo', {},
        'get',
        function (res) {
          _this.setData({
            msg_num: res.data.msg_num,
            infoData: res.data,
            hiddenBindPhoneBlock: res.data.member.phone == '' ? false : true
          })
        }, 'top');
      this.showCoupons();
    }
  },

2. 隐藏官方的tabBar

首先我们要判断版本,如果低于2.5.0的话,
做兼容, 隐藏官方的tabBar用自定义的tabBar;
app.js的onLaunch中检查版本做适配

// 做适配
    wx.getSystemInfo({
      success: function (res) {
        // 判断SDK版本
        let sdkv = res.SDKVersion;
        console.log('当前版本: ' + sdkv)
        let basicsVersion = [2, 5, 0]
        sdkv = sdkv.split(".");
        for (let i in sdkv) {
          if (parseInt(basicsVersion[i]) > parseInt(sdkv[i])) {
            console.warn('当前版本小于2.5.0')
            wx.hideTabBar();
            _this.globalData.useMyTB = true;
          }
        }
      },
    })

3. 控制自己的tabBar组建的显示和隐藏

其实上面代码有写啦 搜索 useMyTB

以上这些就是我对小程序自定义tabBar的理解和应用了, 欢迎指点

参考文档:

小程序官方的tabBar文档
小程序官方的Component文档
小程序app.json的配置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容