今日头条App的Topbar是一个典型的频道管理和切换组件,自己前段时间研究了一番,在微信小程序上也实现了类似的效果。
我们先看具体效果好了 ↓↓↓
	
	
	
接下来,简要说一下实现思路。
	先看视图层,Topbar横向滚动对应的WXML代码如下:
	 
- <scroll-view class="navbar" scroll-x="true" scroll-left="{{scrollNavbarLeft}}">
 - <view class="navbar-item {{ navbarArray[item].type }}" id="{{ item }}" wx:for="{{ navbarShowIndexArray }}" catchtap="onTapNavbar">
 - <view class="navbar-item-wrap">{{ navbarArray[item].text }}</view>
 - </view>
 - <view class="navbar-item visibility-hidden">
 - <view class="navbar-item-wrap">空白</view>
 - </view>
 - </scroll-view>
 - <view class="navbar-arrow-down" catchtap="showChannelSettingModal">
 - <view class="navbar-arrow-down-wrap">
 - <image class="navbar-arrow-icon" src="/images/index/icon_arrow_down.png"></image>
 - </view>
 - </view>
 
	
	scroll-view负责Topbar中各个频道的呈现,所有频道的相关数据都存储在navbarArray这个对象数组里,而数组navbarShowIndexArray里存储了要显示频道在数组navbarArray中的索引。
	
	不难猜测,频道是否选中高亮,与数组navbarArray有关;频道是否显示,与数组navbarShowIndexArray有关。
	
	点击某个频道名称,就会触发对应频道的切换操作。
	
	view.navbar-arrow-down对应的是右上角的向下箭头,可采用fixed定位类型,点击后弹出管理频道的Modal.
	
	 
- <view class="channel-setting-modal {{ channelSettingModalShow }}" hidden="{{ channelSettingModalHide }}">
 - <view class="channel-show-text">
 - <view class="channel-show-text-wrap">显示频道</view>
 - </view>
 - <view class="channel-item" wx:for="{{ navbarShowIndexArray }}">
 - <view class="channel-item-wrap">
 - <view class="channel-item-left">
 - <image class="channel-item-icon-minus {{ !index || navbarShowIndexArray.length < 4 ? 'visibility-hidden' : '' }}" id="{{ item }}.0" src="/images/index/icon_minus.png" catchtap="hideChannel"></image>
 - <view class="channel-item-text">{{ navbarArray[item].text }}</view>
 - </view>
 - <view class="channel-item-up {{ index < 2 ? 'visibility-hidden' : '' }}" id="{{ item }}.00" catchtap="upChannel">上移</view>
 - </view>
 - </view>
 - <view class="channel-hide-text">
 - <view class="channel-hide-text-wrap">隐藏频道</view>
 - </view>
 - <view class="channel-item" wx:for="{{ navbarHideIndexArray }}">
 - <view class="channel-item-wrap">
 - <view class="channel-item-left">
 - <image class="channel-item-icon-plus" id="{{ item }}.0" src="/images/index/icon_plus.png" catchtap="showChannel"></image>
 - <view class="channel-item-text">{{ navbarArray[item].text }}</view>
 - </view>
 - <view class="channel-item-up visibility-hidden">上移</view>
 - </view>
 - </view>
 - </view>
 
	
	
	
	在这个管理频道的Modal里,通过改变数组navbarShowIndexArray来控制频道是否显示和显示顺序,同时,需要另外一个数组navbarHideIndexArray来存储隐藏的频道。
	
	Modal显示的时候,Topbar需要被另一个写有“频道设置”字样的Bar覆盖。
	 
- <view class="channel-setting {{ channelSettingShow }}">
 - <view class="channel-setting-text">频道设置</view>
 - <view class="navbar-arrow-up" catchtap="hideChannelSettingModal">
 - <image class="navbar-arrow-icon navbar-arrow-icon-up" src="/images/index/icon_arrow_up.png"></image>
 - </view>
 - </view>
 
	然后,我们来看逻辑层的实现。初始化的部分data如下:
	 
- data: {
 - navbarArray: [{
 - text: '推荐',
 - type: 'navbar-item-active'
 - }, {
 - text: '热点',
 - type: ''
 - }, {
 - text: '视频',
 - type: ''
 - }, {
 - text: '图片',
 - type: ''
 - }, {
 - text: '段子',
 - type: ''
 - }, {
 - text: '社会',
 - type: ''
 - }, {
 - text: '娱乐',
 - type: ''
 - }, {
 - text: '科技',
 - type: ''
 - }, {
 - text: '体育',
 - type: ''
 - }, {
 - text: '汽车',
 - type: ''
 - }, {
 - text: '财经',
 - type: ''
 - }, {
 - text: '搞笑',
 - type: ''
 - }],
 - navbarShowIndexArray: Array.from(Array(12).keys()),
 - navbarHideIndexArray: [],
 - channelSettingShow: '',
 - channelSettingModalShow: '',
 - channelSettingModalHide: true
 - }
 
	
	
	
	11的数组,刚好是数组navbarArray的所有元素的索引。显然,初始化的结果是所有频道都将显示。
	
	为了实现频道个性化配置的保存,navbarShowIndexArray还需要通过小程序的数据缓存API储存起来。
	
	storeNavbarShowIndexArray: function() {
	
	    const that = this;
	
	    wx.setStorage({
	
	        key: 'navbarShowIndexArray',
	
	        data: that.data.navbarShowIndexArray
	
	    });
	
	}
	
	切换频道的函数如下:
	 
- switchChannel: function(targetChannelIndex) {
 - this.getArticles(targetChannelIndex);
 - let navbarArray = this.data.navbarArray;
 - navbarArray.forEach((item, index, array) => {
 - item.type = '';
 - if (index === targetChannelIndex) {
 - item.type = 'navbar-item-active';
 - }
 - });
 - this.setData({
 - navbarArray: navbarArray,
 - currentChannelIndex: targetChannelIndex
 - });
 - }
 
	
	
	
	这样,频道的管理和简单切换我们就实现了。
	
	但是,到此为止,频道的切换只能通过点击对应Topbar中频道那一小块区域来实现,要是在正文区域左滑和右滑也能切换频道就好了。
	
	一个容易想到的思路是,在正文区域绑定touch事件,通过坐标判断滑动方向,然后使Topbar中当前频道的上一个或下一个频道高亮,同时,控制Topbar横向滚动合适的偏移长度,以确保切换后的频道能出现在视图区域。
	 
- onTouchstartArticles: function(e) {
 - this.setData({
 - 'startTouchs.x': e.changedTouches[0].clientX,
 - 'startTouchs.y': e.changedTouches[0].clientY
 - });
 - },
 - onTouchendArticles: function(e) {
 - let deltaX = e.changedTouches[0].clientX - this.data.startTouchs.x;
 - let deltaY = e.changedTouches[0].clientY - this.data.startTouchs.y;
 - if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
 - let deltaNavbarIndex = deltaX > 0 ? -1 : 1;
 - let currentChannelIndex = this.data.currentChannelIndex;
 - let navbarShowIndexArray = this.data.navbarShowIndexArray;
 - let targetChannelIndexOfNavbarShowIndexArray = navbarShowIndexArray.indexOf(currentChannelIndex) + deltaNavbarIndex;
 - let navbarShowIndexArrayLength = navbarShowIndexArray.length;
 - if (targetChannelIndexOfNavbarShowIndexArray >= 0 && targetChannelIndexOfNavbarShowIndexArray <= navbarShowIndexArrayLength - 1) {
 - let targetChannelIndex = navbarShowIndexArray[targetChannelIndexOfNavbarShowIndexArray];
 - if (navbarShowIndexArrayLength > 6) {
 - let scrollNavbarLeft;
 - if (targetChannelIndexOfNavbarShowIndexArray < 5) {
 - scrollNavbarLeft = 0;
 - } else if (targetChannelIndexOfNavbarShowIndexArray === navbarShowIndexArrayLength - 1) {
 - scrollNavbarLeft = this.rpx2px(110 * (navbarShowIndexArrayLength - 6));
 - } else {
 - scrollNavbarLeft = this.rpx2px(110 * (targetChannelIndexOfNavbarShowIndexArray - 4));
 - }
 - this.setData({
 - scrollNavbarLeft: scrollNavbarLeft
 - });
 - }
 - this.switchChannel(targetChannelIndex);
 - }
 - }
 - }
 
	
	
	 
新闻热点
疑难解答