微信小程序:自定义可滑动导航栏

2019-06-19  本文已影响0人  like26th

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

源码

上一篇下一篇

猜你喜欢

热点阅读