微信小程序自定义tabBar/兼容低版本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、/custom-tab-bar/index.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>

2、/custom-tab-bar/index.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
      })
    },
  }
})

3、 /custom-tab-bar/index.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;
}

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": "我的"
      }
    ]
  },

在用到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

1、 /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'
          })
        }
      }
    },
  }
})

2、 /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>

3、 在使用的页面:

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();
    }
  },

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

参考文档

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

猜你喜欢

转载自blog.csdn.net/weixin_37880401/article/details/89847691