终于入坑小程序了,最近开发小程序,要实现一个 tab 切换效果,看到知乎热榜上的滑动切换 tab 效果还挺舒服的,刚好项目也需要这个功能,就尝试实现一下,首先需要一些基本知识点

循环渲染数据

小程序有类似 vue 的写法,但是还有区别的

data: {
arr: [1, 2, 3];
}
<view wx:for="{{arr}}">{{ item }}</view>;

wx:for-item

默认情况下遍历的每一项用 item 来获取,如果要指定 item 的名称,可以用wx:for-item

<view wx:for="{{arr}}" wx:for-item="i">{{i}}</view>

wx:key

同 vue 一样,设置一个 key 属性可以优化列表渲染,微信也可以设置一个 key,如果数组是每一项是唯一的字符串或者数字,可以使用 wx:key="*this"

<view wx:for="{{arr}}" wx:key="*this">{{item}}</view>

如果是对象数组,一般使用唯一值的字段,例如 id

{
data: {
list: [
{ id: 1, name: "list1" },
{ id: 2, name: "list2" },
];
}
}
<view wx:for="{{list}}" wx:key="id">{{item.name}}</view>

进入正题,按照需求定义两个 tab 选项卡,如果 tab 选项过多可以使用scroll-view

<view class='flex-row tabbar'>
<text id="tabitemRecharge" class='tabitem {{activeTabId=="tabitemRecharge"?"tabitem-active":""}}' bindtap='tabclick'>充值记录</text>
<text id="tabitemConsume" class='tabitem {{activeTabId=="tabitemConsume"?"tabitem-active":""}}' bindtap='tabclick'>消费记录</text>
<view class='tabindicator' animation="{{indicatorAnim}}"></view>
</view>
<swiper class='scrollview-content' current-item-id='tabitemRecharge' bindchange='tabChange'>
<swiper-item item-id="tabitemRecharge">
<scroll-view scroll-y="true" class="tab-content recharge-list">
<view class='list-item recharge-item' wx:for="{{rechargeList}}" wx:for-item="rItem" wx:key="id">{{rItem.name}}</view>
</scroll-view>
</swiper-item>
<swiper-item item-id="tabitemConsume">
<scroll-view scroll-y="true" class="tab-content recharge-list">
<view class='list-item consume-item' wx:for="{{consumeList}}" wx:for-item="rItem" wx:key="id">{{rItem.name}}</view>
</scroll-view>
</swiper-item>
</swiper>

tab 页面的数据结构如下

{
data: {
  tabitemConsume: {},        // 存放tab选项卡的rect,键对应的内容swiper-item的item-id
  tabitemRecharge: {},        // 存放tab选项卡的rect,键对应的内容swiper-item的item-id
activeTabId: null,
  rechargeList: [],
  consumeList: []
}
}

这里命名属性的方式是可以方便通过 swiper-item 的 item-id 来获取对应的 tab 选项卡的位置信息,例如

var rect = this.data[swiper-item-id];

wx.createSelectorQuery

要实现 tab 指示器的大小和位置动画,需要测量每个 tab 的 rect(大小和位置)。微信没有提供丰富的 dom 操作 api,但是提供了通过wx.createSelectorQuery来获取节点的信息

onReady() {
var query = wx.createSelectorQuery().in(this),
_this = this;
_this.animation = wx.createAnimation()
query.select('#tabitemConsume').boundingClientRect(function (rect) {
_this.setData({
tabitemConsume: rect
});
})
query.select('#tabitemRecharge').boundingClientRect(function (rect) {
_this.setData({
tabitemRecharge: rect
});
_this.setActiveTab('tabitemRecharge');
})
query.exec();
}

wx.createAnimation

小程序的动画用wx.createAnimation来实现,给元素设置一个 animation 属性,通过 export 方法来给 animation 属性赋值

每次调用 export 方法都会清空上一次设置动画,举个例子

<view style="width: 20px; height: 4px; background: #333;" animation="{{anim}}"></view>;
this.animation = wx.createAnimation({
timingFunction: "ease",
delay: 0,
});
// width变成200,x位置移动到100的动画
this.animation.width(200).translate(100, 0).step();
this.setData({
anim: this.animation.export(),
});

效果如下

tab 指示器的动画

通过上面的 wx.createAnimation 就可以实现指示器的切换效果了

// tab选项卡激活设置动画
setActiveTab(id) {
  var rect = this.data[id];
  if (rect) {
    this.animation.width(rect.width).translate(rect.left, 0);
    this.setData({
      activeTabId: id,        // 激活的swiper视图
      indicatorAnim: this.animation.step().export()
    })
  }
}

tab 选项卡内容块

由于要实现滑动切换 tab,用 swiper 来实现横向滑动,scroll-view 来实现纵向滚动

swiper 组件

swiper是滑块视图容器组件,可以用来实现幻灯片轮播效果等等。

<swiper class='scrollview-content' current-item-id='{{activeTabId}}' bindchange='tabChange'>
<swiper-item item-id="tabitemRecharge"></swiper-item>
<swiper-item item-id="tabitemConsume"></swiper-item>
</swiper>

swiper 有一个 current-item-id 属性,通过匹配 swiper-item 的 item-id 属性来设置 swiper 指定的视图内容,在 bindchange 事件获取视图的 id 指定对应的 swiper 的视图即可

tabChange(e) {
  var id = e.detail.currentItemId;
  this.setActiveTab(id);
}

附上效果地址