首页 > 课堂 > 小程序 > 正文

微信小程序中仿今日头条AppTopbar实现教程

2020-03-21 16:19:15
字体:
来源:转载
供稿:网友

今日头条App的Topbar是一个典型的频道管理和切换组件,自己前段时间研究了一番,在微信小程序上也实现了类似的效果。

我们先看具体效果好了 ↓↓↓

微信小程序,今日头条,AppTopbar

微信小程序,今日头条,AppTopbar

微信小程序,今日头条,AppTopbar

接下来,简要说一下实现思路。

先看视图层,Topbar横向滚动对应的WXML代码如下:
 

  1. <scroll-view class="navbar" scroll-x="true" scroll-left="{{scrollNavbarLeft}}"> 
  2.  
  3.     <view class="navbar-item {{ navbarArray[item].type }}" id="{{ item }}" wx:for="{{ navbarShowIndexArray }}" catchtap="onTapNavbar"> 
  4.  
  5.         <view class="navbar-item-wrap">{{ navbarArray[item].text }}</view> 
  6.  
  7.     </view> 
  8.  
  9.     <view class="navbar-item visibility-hidden"> 
  10.  
  11.         <view class="navbar-item-wrap">空白</view> 
  12.  
  13.     </view> 
  14.  
  15. </scroll-view> 
  16.  
  17. <view class="navbar-arrow-down" catchtap="showChannelSettingModal"> 
  18.  
  19.     <view class="navbar-arrow-down-wrap"> 
  20.  
  21.         <image class="navbar-arrow-icon" src="/images/index/icon_arrow_down.png"></image> 
  22.  
  23.     </view> 
  24.  
  25. </view> 


scroll-view负责Topbar中各个频道的呈现,所有频道的相关数据都存储在navbarArray这个对象数组里,而数组navbarShowIndexArray里存储了要显示频道在数组navbarArray中的索引。

不难猜测,频道是否选中高亮,与数组navbarArray有关;频道是否显示,与数组navbarShowIndexArray有关。

点击某个频道名称,就会触发对应频道的切换操作。

view.navbar-arrow-down对应的是右上角的向下箭头,可采用fixed定位类型,点击后弹出管理频道的Modal.

 

  1. <view class="channel-setting-modal {{ channelSettingModalShow }}" hidden="{{ channelSettingModalHide }}"> 
  2.  
  3.     <view class="channel-show-text"> 
  4.  
  5.         <view class="channel-show-text-wrap">显示频道</view> 
  6.  
  7.     </view> 
  8.  
  9.     <view class="channel-item" wx:for="{{ navbarShowIndexArray }}"> 
  10.  
  11.         <view class="channel-item-wrap"> 
  12.  
  13.             <view class="channel-item-left"> 
  14.  
  15.                 <image class="channel-item-icon-minus {{ !index || navbarShowIndexArray.length < 4 ? 'visibility-hidden' : '' }}" id="{{ item }}.0" src="/images/index/icon_minus.png" catchtap="hideChannel"></image> 
  16.  
  17.                 <view class="channel-item-text">{{ navbarArray[item].text }}</view> 
  18.  
  19.             </view> 
  20.  
  21.             <view class="channel-item-up {{ index < 2 ? 'visibility-hidden' : '' }}" id="{{ item }}.00" catchtap="upChannel">上移</view> 
  22.  
  23.         </view> 
  24.  
  25.     </view> 
  26.  
  27.     <view class="channel-hide-text"> 
  28.  
  29.         <view class="channel-hide-text-wrap">隐藏频道</view> 
  30.  
  31.     </view> 
  32.  
  33.     <view class="channel-item" wx:for="{{ navbarHideIndexArray }}"> 
  34.  
  35.         <view class="channel-item-wrap"> 
  36.  
  37.             <view class="channel-item-left"> 
  38.  
  39.                 <image class="channel-item-icon-plus" id="{{ item }}.0" src="/images/index/icon_plus.png" catchtap="showChannel"></image> 
  40.  
  41.                 <view class="channel-item-text">{{ navbarArray[item].text }}</view> 
  42.  
  43.             </view> 
  44.  
  45.             <view class="channel-item-up visibility-hidden">上移</view> 
  46.  
  47.         </view> 
  48.  
  49.     </view> 
  50.  
  51. </view> 




在这个管理频道的Modal里,通过改变数组navbarShowIndexArray来控制频道是否显示和显示顺序,同时,需要另外一个数组navbarHideIndexArray来存储隐藏的频道。

Modal显示的时候,Topbar需要被另一个写有“频道设置”字样的Bar覆盖。
 

  1. <view class="channel-setting {{ channelSettingShow }}"> 
  2.  
  3.     <view class="channel-setting-text">频道设置</view> 
  4.  
  5.     <view class="navbar-arrow-up" catchtap="hideChannelSettingModal"> 
  6.  
  7.         <image class="navbar-arrow-icon navbar-arrow-icon-up" src="/images/index/icon_arrow_up.png"></image> 
  8.  
  9.     </view> 
  10.  
  11. </view> 

然后,我们来看逻辑层的实现。初始化的部分data如下:
 

  1. data: { 
  2.  
  3.     navbarArray: [{ 
  4.  
  5.         text: '推荐'
  6.  
  7.         type: 'navbar-item-active' 
  8.  
  9.     }, { 
  10.  
  11.         text: '热点'
  12.  
  13.         type: '' 
  14.  
  15.     }, { 
  16.  
  17.         text: '视频'
  18.  
  19.         type: '' 
  20.  
  21.     }, { 
  22.  
  23.         text: '图片'
  24.  
  25.         type: '' 
  26.  
  27.     }, { 
  28.  
  29.         text: '段子'
  30.  
  31.         type: '' 
  32.  
  33.     }, { 
  34.  
  35.         text: '社会'
  36.  
  37.         type: '' 
  38.  
  39.     }, { 
  40.  
  41.         text: '娱乐'
  42.  
  43.         type: '' 
  44.  
  45.     }, { 
  46.  
  47.         text: '科技'
  48.  
  49.         type: '' 
  50.  
  51.     }, { 
  52.  
  53.         text: '体育'
  54.  
  55.         type: '' 
  56.  
  57.     }, { 
  58.  
  59.         text: '汽车'
  60.  
  61.         type: '' 
  62.  
  63.     }, { 
  64.  
  65.         text: '财经'
  66.  
  67.         type: '' 
  68.  
  69.     }, { 
  70.  
  71.         text: '搞笑'
  72.  
  73.         type: '' 
  74.  
  75.     }], 
  76.  
  77.     navbarShowIndexArray: Array.from(Array(12).keys()), 
  78.  
  79.     navbarHideIndexArray: [], 
  80.  
  81.     channelSettingShow: ''
  82.  
  83.     channelSettingModalShow: ''
  84.  
  85.     channelSettingModalHide: true 
  86.  




11的数组,刚好是数组navbarArray的所有元素的索引。显然,初始化的结果是所有频道都将显示。

为了实现频道个性化配置的保存,navbarShowIndexArray还需要通过小程序的数据缓存API储存起来。

storeNavbarShowIndexArray: function() {

    const that = this;

    wx.setStorage({

        key: 'navbarShowIndexArray',

        data: that.data.navbarShowIndexArray

    });

}

切换频道的函数如下:
 

  1. switchChannel: function(targetChannelIndex) { 
  2.  
  3.     this.getArticles(targetChannelIndex); 
  4.  
  5.     let navbarArray = this.data.navbarArray; 
  6.  
  7.     navbarArray.forEach((item, index, array) => { 
  8.  
  9.         item.type = ''
  10.  
  11.         if (index === targetChannelIndex) { 
  12.  
  13.             item.type = 'navbar-item-active'
  14.  
  15.         } 
  16.  
  17.     }); 
  18.  
  19.     this.setData({ 
  20.  
  21.         navbarArray: navbarArray, 
  22.  
  23.         currentChannelIndex: targetChannelIndex 
  24.  
  25.     }); 
  26.  




这样,频道的管理和简单切换我们就实现了。

但是,到此为止,频道的切换只能通过点击对应Topbar中频道那一小块区域来实现,要是在正文区域左滑和右滑也能切换频道就好了。

一个容易想到的思路是,在正文区域绑定touch事件,通过坐标判断滑动方向,然后使Topbar中当前频道的上一个或下一个频道高亮,同时,控制Topbar横向滚动合适的偏移长度,以确保切换后的频道能出现在视图区域。
 

  1. onTouchstartArticles: function(e) { 
  2.  
  3.     this.setData({ 
  4.  
  5.         'startTouchs.x': e.changedTouches[0].clientX, 
  6.  
  7.         'startTouchs.y': e.changedTouches[0].clientY 
  8.  
  9.     }); 
  10.  
  11. }, 
  12.  
  13. onTouchendArticles: function(e) { 
  14.  
  15.     let deltaX = e.changedTouches[0].clientX - this.data.startTouchs.x; 
  16.  
  17.     let deltaY = e.changedTouches[0].clientY - this.data.startTouchs.y; 
  18.  
  19.     if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) { 
  20.  
  21.         let deltaNavbarIndex = deltaX > 0 ? -1 : 1; 
  22.  
  23.         let currentChannelIndex = this.data.currentChannelIndex; 
  24.  
  25.         let navbarShowIndexArray = this.data.navbarShowIndexArray; 
  26.  
  27.         let targetChannelIndexOfNavbarShowIndexArray = navbarShowIndexArray.indexOf(currentChannelIndex) + deltaNavbarIndex; 
  28.  
  29.         let navbarShowIndexArrayLength = navbarShowIndexArray.length; 
  30.  
  31.         if (targetChannelIndexOfNavbarShowIndexArray >= 0 && targetChannelIndexOfNavbarShowIndexArray <= navbarShowIndexArrayLength - 1) { 
  32.  
  33.             let targetChannelIndex = navbarShowIndexArray[targetChannelIndexOfNavbarShowIndexArray]; 
  34.  
  35.             if (navbarShowIndexArrayLength > 6) { 
  36.  
  37.                 let scrollNavbarLeft; 
  38.  
  39.                 if (targetChannelIndexOfNavbarShowIndexArray < 5) { 
  40.  
  41.                     scrollNavbarLeft = 0; 
  42.  
  43.                 } else if (targetChannelIndexOfNavbarShowIndexArray === navbarShowIndexArrayLength - 1) { 
  44.  
  45.                     scrollNavbarLeft = this.rpx2px(110 * (navbarShowIndexArrayLength - 6)); 
  46.  
  47.                 } else { 
  48.  
  49.                     scrollNavbarLeft = this.rpx2px(110 * (targetChannelIndexOfNavbarShowIndexArray - 4)); 
  50.  
  51.                 } 
  52.  
  53.                 this.setData({ 
  54.  
  55.                     scrollNavbarLeft: scrollNavbarLeft 
  56.  
  57.                 }); 
  58.  
  59.             } 
  60.  
  61.             this.switchChannel(targetChannelIndex); 
  62.  
  63.         } 
  64.  
  65.     } 
  66.  



 


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表