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

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

源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容