美文网首页
微信小程序:自定义可滑动导航栏

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

作者: like26th | 来源:发表于2019-06-19 11:37 被阅读0次

    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

    源码

    相关文章

      网友评论

          本文标题:微信小程序:自定义可滑动导航栏

          本文链接:https://www.haomeiwen.com/subject/nfbkfctx.html