如何在微信小程序中实现今日头条App那样的Topbar

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

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

wx-topbar-screenshot-1
wx-topbar-screenshot-2
wx-topbar-screenshot-3

这个项目(wx-topbar)已经放在GitHub上了——点此前往,欢迎学习交流。

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

先看视图层,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
}

navbar-item-active是一个可使频道高亮的ClassnavbarShowIndexArray初始化的结果是一个0到11的数组,刚好是数组navbarArray的所有元素的索引。显然,初始化的结果是所有频道都将显示。

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

storeNavbarShowIndexArray: function() {
    wx.setStorage({
        key: 'navbarShowIndexArray',
        data: this.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);
        }
    }
}

更多的技术细节,请移步GitHub——点此前往

个人技术博客 biebu.xin,原文链接——如何在微信小程序中实现今日头条App那样的Topbar

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

推荐阅读更多精彩内容