今日頭條App的Topbar是一個典型的頻道管理和切換組件,自己前段時間研究了一番,在微信小程序上也實現(xiàn)了類似的效果。
我們先看具體效果好了 ↓↓↓
這個項目(wx-topbar)已經(jīng)放在GitHub上了——點此前往,歡迎學(xué)習(xí)交流。
接下來,簡要說一下實現(xiàn)思路。
先看視圖層,Topbar橫向滾動對應(yīng)的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負責(zé)Topbar中各個頻道的呈現(xiàn),所有頻道的相關(guān)數(shù)據(jù)都存儲在navbarArray這個對象數(shù)組里,而數(shù)組navbarShowIndexArray里存儲了要顯示頻道在數(shù)組navbarArray中的索引。
不難猜測,頻道是否選中高亮,與數(shù)組navbarArray有關(guān);頻道是否顯示,與數(shù)組navbarShowIndexArray有關(guān)。
點擊某個頻道名稱,就會觸發(fā)對應(yīng)頻道的切換操作。
view.navbar-arrow-down對應(yīng)的是右上角的向下箭頭,可采用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里,通過改變數(shù)組navbarShowIndexArray來控制頻道是否顯示和顯示順序,同時,需要另外一個數(shù)組navbarHideIndexArray來存儲隱藏的頻道。
Modal顯示的時候,Topbar需要被另一個寫有“頻道設(shè)置”字樣的Bar覆蓋。
<view class="channel-setting {{ channelSettingShow }}">
<view class="channel-setting-text">頻道設(shè)置</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>
然后,我們來看邏輯層的實現(xiàn)。初始化的部分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: '財經(jīng)',
type: ''
}, {
text: '搞笑',
type: ''
}],
navbarShowIndexArray: Array.from(Array(12).keys()),
navbarHideIndexArray: [],
channelSettingShow: '',
channelSettingModalShow: '',
channelSettingModalHide: true
}
navbar-item-active是一個可使頻道高亮的Class,navbarShowIndexArray初始化的結(jié)果是一個0到11的數(shù)組,剛好是數(shù)組navbarArray的所有元素的索引。顯然,初始化的結(jié)果是所有頻道都將顯示。
為了實現(xiàn)頻道個性化配置的保存,navbarShowIndexArray還需要通過小程序的數(shù)據(jù)緩存API儲存起來。
storeNavbarShowIndexArray: function() {
const that = this;
wx.setStorage({
key: 'navbarShowIndexArray',
data: that.data.navbarShowIndexArray
});
}
切換頻道的函數(shù)如下:
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
});
}
這樣,頻道的管理和簡單切換我們就實現(xiàn)了。
但是,到此為止,頻道的切換只能通過點擊對應(yīng)Topbar中頻道那一小塊區(qū)域來實現(xiàn),要是在正文區(qū)域左滑和右滑也能切換頻道就好了。
一個容易想到的思路是,在正文區(qū)域綁定touch事件,通過坐標判斷滑動方向,然后使Topbar中當(dāng)前頻道的上一個或下一個頻道高亮,同時,控制Topbar橫向滾動合適的偏移長度,以確保切換后的頻道能出現(xiàn)在視圖區(qū)域。
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);
}
}
}