app一般会有这样的导航栏,标题下的指示器(横线)会随着页面的移动而移动。
indicator1.gif最近要做一个小程序,也需要一个导航栏,但翻了一些网上的demo,都没有类似的效果。android中可以通过TabLayout + ViewPager实现,于是决定照猫画虎,写一个小程序的自定义TabLayout。
思路是,小程序提供了swiper组件可以实现左右滑动的效果,那么只要再有一个view可以随着swiper页面移动就好了。
在项目中新建一个page,并把该page声明为自定义控件。
//json
{
"component":true
}
导航栏的标题应该是调用者用参数的方式告诉我们的,所以tablayout需要一个属性接收标题。
//js
properties: {
titles:{
type:Array,
value: [],
}
}
tablayout布局分为两部分,标题部分和滑动页面部分。
标题部分包括导航栏的标题和标题下的会移动的小横线(这里称为indicator),标题绑定titles数据,同时给indicator绑定一个动画。
//wxml
<!-- title -->
<view id="head" style='display:flex; flex-direction:column; align-items:center;'>
<view>
<view style='height:55rpx; display:flex; flex-direction:row; text-align:center;'>
<view wx:for="{{titles}}" style='width:100rpx;'>
<text data-index='{{index}}' bindtap='clickTitle'>{{item}}</text>
</view>
</view>
<!-- indicator -->
<view style='width:100rpx; height:5rpx; background:lightgray;' animation="{{animation}}"></view>
</view>
</view>
滑动页面部分,相当于viewpager的角色,为了保证和标题一致,同样需要绑定titles数据,监听swiper滑动和滑动结束状态。在<swiper-item>中添加<slot>标签,使调用者可以动态为swiper添加布局,可以用for循环的index作为<slot>的key。
//wxml
<!-- page -->
<swiper style='height:100%;' current="{{swiperIndex}}" bindtransition="swiperTrans" bindanimationfinish="swiperAnimationfinish" bindchange='swiperChange'>
<view wx:for="{{titles}}" wx:key="*this">
<swiper-item style='background:lightblue; display:flex; align-items:center; justify-content:center'>
<slot name="{{index}}"></slot>
</swiper-item>
</view>
</swiper>
因为可能有多个页面,所以还要添加一个多slot支持
//js
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
}
通过swiper中页面的滑动距离计算indicator的需要移动多少,除需要知道swiper的滑动距离外,还需要知道每个swiper-item的宽度和indicator所在布局的宽度,通过两个宽度的比例计算出indicator的位移。
这里swiper宽度为屏幕宽度,可在页面加载时获取,indicator滑动范围可自行定义。不过有个问题,通过wx.getSystemInfoSync().screenWidth获取的屏幕宽度单位是px,而给indecator赋值时单位是rpx,单位不同不能用于计算,好在小程序默认的屏幕宽度为750rpx,可用于计算比例。
data:{
//屏幕宽度
screenWidth:"",
//微信规定的屏幕宽度750 rpx
wxScreenWidth:750,
//指示器滑动范围宽度,单位宽度
indicatorLayoutWidth:100
}
...
lifetimes: {
attached() {
// 获取屏幕宽度
var that = this;
that.setData({ screenWidth: wx.getSystemInfoSync().screenWidth });
}
}
在swiper滑动时需要判断滑动位置,在左右尽头是不能继续滑动的,所以在在swiper滑动完成后判断一下状态。
data:{
//标题 swiper-item 所在位置
titleIndex: 0,
//滑动状态:滑动到左边(1)、滑动到右边(2)、其他位置(0)
scrollStatus:1
}
...
swiperAnimationfinish: function(e) {
var that = this;
that.setData({
titleIndex: e.detail.current
});
//计算指示器位移状态
if (that.data.titleIndex == (that.data.titles.length-1)) {
// console.log("move to the right")
that.setData({ scrollStatus: 2 });
} else if (that.data.titleIndex == 0) {
// console.log("move to the left")
that.setData({ scrollStatus: 1 });
}else {
that.setData({ scrollStatus: 0 });
}
}
之后就可以通过监听swiper的滑动,让indicator一起联动了。
//tablayout.js
data:{
//indiator 动画
animation: "",
}
...
methods:{
swiperTrans:function (e) {
var that = this;
// swipter位移 中间变量
var dx;
//e.detail.dx 页面滑动距离,手指向左滑动距离为正,反之为负
if (e.detail.dx >= 0)
if (that.data.scrollStatus == 2)//页面处于最右,且仍向左滑动时,页面位置保持在最右。
dx = that.data.screenWidth * that.data.titleIndex;
else
dx = e.detail.dx + that.data.screenWidth * that.data.titleIndex;
else if (that.data.scrollStatus == 1) //页面在初始位置,且仍向右滑动时,页面停留在初始位置。
dx = 0
else
dx = e.detail.dx + that.data.screenWidth * that.data.titleIndex;
//indicator与swipter之间移动比例
var scale = (that.data.indicatorLayoutWidth / that.data.wxScreenWidth).toFixed(2);//保留两位小数,否则indicator动画有误差
//indicator 位移
var ds = dx * scale;
this.transIndicator(ds);
},
//indicator 平移动画
transIndicator(x) {
let option = {
duration: 100,
timingFunction: 'linear'
};
this.animation = wx.createAnimation(option);
this.animation.translateX(x).step();
this.setData({
animation: this.animation.export()
})
}
}
在点击小标题时,swiper的滑动到对应页面。
//js
clickTitle(e) {
//点击切换卡片
var that = this;
that.setData({
//swiper 绑定了 current="{{swiperIndex}}"
swiperIndex: e.currentTarget.dataset.index
});
}
此时就可以在其他页面进行调用了,调用的时候按模块引入其他页面就可以了。
<import src="../extend/extend.wxml"/>
<tablayout titles="{{titles}}">
<view slot="0"><template is="extend"></template></view>
<view slot="1"><template is="extend"></template></view>
<view slot="2"><template is="extend"></template></view>
<view slot="3"><template is="extend"></template></view>
</tablayout>
实现效果为
indicator2.gif
网友评论