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