问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

微信小程序开发:实现列表滚动上下联动效果的方法详解

创作时间:
作者:
@小白创作中心

微信小程序开发:实现列表滚动上下联动效果的方法详解

引用
1
来源
1.
https://www.finclip.com/news/f/85230.html

在微信小程序开发中,实现列表滚动上下联动效果是一个常见的需求。本文将详细介绍如何通过微信小程序原生的scroll-view组件实现这一功能,包括原理介绍、页面布局、样式设计和逻辑处理等关键步骤。

1. 背景

最近在开发公司的一款小程序时,遇到了一个需求:在列表滚动时,顶部的tab栏需要跟随一起联动;同时,点击tab栏时,列表数据也需要相应地联动。下面是实现的效果图:

  • 顶部的头部区域不跟随列表滚动
  • 头部区域以下属于滚动区域

2. 实现

2.1 原理介绍

实现这一功能主要借助微信小程序原生的scroll-view组件。具体来说:

  • 使用scroll-into-view属性,可以实现点击顶部的tab栏,将页面滚动到指定的列表位置
  • 使用bindscroll事件,可以获取当前页面滚动的距离,根据滚动的距离来切换tab栏

2.2 页面布局代码

界面整体布局分为两部分:头部固定区域和可滚动列表区域。其中,可滚动列表区域的标题栏在滚动到一定距离后需要固定在顶部。

<!--index.wxml-->
<view class="list">
  <!--顶部固定区域-->
  <view style="height: 88rpx;width: 100%;background-color: burlywood;text-align: center;">头部区域</view>
  <!--可滚动区域-->
  <scroll-view scroll-y="true"
               style="width: 100%; height: {{scrollAreaHeight}}px;"
               bindscroll="scroll"
               scroll-into-view="{{scrollToItem}}"
               scroll-with-animation="true"
               scroll-top="{{scrollTop}}">
    <!--水平滚动的tab栏-->
    <scroll-view scroll-x="true"
                 style="height: 88rpx;width: 100%;">
      <view class="head-area {{float ? 'head-float' : ''}}">
        <view class="head-area-item {{curSelectTab === index ? 'head-area-item-select' : ''}}"
              wx:for="{{appGroupList}}"
              bindtap="tabClick"
              data-index="{{index}}">
          {{item.name}}
        </view>
      </view>
    </scroll-view>
    <!--数据列表-->
    <view class="list-group"
          style="height: {{listGroupHeight}}px;">
      <view class="list-group-item"
            id="v_{{index}}"
            wx:for="{{appGroupList}}"
            data-index="{{index}}">
        <view class="group-name">
          {{item.name}}
        </view>
        <view class="group-children">
          <view wx:for="{{item.children}}"
                class="group-children-item"
                style="width: {{itemWidth}}px;">
            <image src="{{item.url}}"></image>
            <view>{{item.name}}</view>
          </view>
        </view>
      </view>
    </view>
  </scroll-view>
</view>

在布局代码中有几个关键点需要注意:

  1. scrollAreaHeight:滚动区域的高度计算。通过获取当前设备的窗口高度减去顶部固定区域的高度
  2. 水平tab栏是否置顶:根据页面的滚动距离来判断,如果滚动距离大于或等于水平tab栏的高度,则置顶
  3. 数据列表的id设置:id="v_{{index}}",后续点击tab栏滚动到指定位置就是根据这个id实现的

2.3 样式代码

/*index.wxss*/
.list {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.head-area {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  height: 88rpx;
  width: 100%;
  padding: 0 10;
}

.head-area-item {
  display: flex;
  height: 88rpx;
  text-align: center;
  width: 150rpx;
  align-items: center;
  justify-content: center;
}

.head-area-item-select {
  color:#09bb07;
}

image {
  width: 88rpx;
  height: 88rpx;
}

.list-group {
  display: flex;
  width: 100%;
  height: 1000%;
  flex-direction: column;
}

.list-group-item {
  display: flex;
  width: 100%;
  background-color:#aaa;
  flex-direction: column;
}

.group-name {
  height: 88rpx;
  display: flex;
  text-align: center;
  align-items: center;
  margin-left: 20rpx;
}

.group-children {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  width: 100%;
}

.group-children-item {
  height: 160rpx;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.head-float {
  position: fixed;
  top: 88rpx;
  background-color:#ffffff;
}

2.4 逻辑代码

// index.js
Page({
  heightArr: [], // 记录scroll-view滚动过程中距离顶部的高度
  distance: 0,
  data: {
    appGroupList:[
      {name:"分组01",children:[{"name":"测试0","url":"/images/bluetooth.png"},
      {"name":"测试1","url":"/images/bluetooth.png"},
      {"name":"测试2","url":"/images/bluetooth.png"},
      {"name":"测试3","url":"/images/bluetooth.png"},
      {"name":"测试4","url":"/images/bluetooth.png"},
      {"name":"测试5","url":"/images/bluetooth.png"},
      {"name":"测试6","url":"/images/bluetooth.png"},
      {"name":"测试7","url":"/images/bluetooth.png"}]},
      {name:"分组02",children:[{"name":"测试0","url":"/images/bluetooth.png"},
      {"name":"测试1","url":"/images/bluetooth.png"},
      {"name":"测试2","url":"/images/bluetooth.png"},
      {"name":"测试3","url":"/images/bluetooth.png"},
      {"name":"测试4","url":"/images/bluetooth.png"},
      {"name":"测试5","url":"/images/bluetooth.png"},
      {"name":"测试6","url":"/images/bluetooth.png"},
      {"name":"测试7","url":"/images/bluetooth.png"}]},
      {name:"分组03",children:[{"name":"测试0","url":"/images/bluetooth.png"},
      {"name":"测试1","url":"/images/bluetooth.png"},
      {"name":"测试2","url":"/images/bluetooth.png"},
      {"name":"测试3","url":"/images/bluetooth.png"},
      {"name":"测试4","url":"/images/bluetooth.png"},
      {"name":"测试5","url":"/images/bluetooth.png"},
      {"name":"测试6","url":"/images/bluetooth.png"},
      {"name":"测试7","url":"/images/bluetooth.png"}]},
      {name:"分组04",children:[{"name":"测试0","url":"/images/bluetooth.png"},
      {"name":"测试1","url":"/images/bluetooth.png"},
      {"name":"测试2","url":"/images/bluetooth.png"},
      {"name":"测试3","url":"/images/bluetooth.png"},
      {"name":"测试4","url":"/images/bluetooth.png"},
      {"name":"测试5","url":"/images/bluetooth.png"},
      {"name":"测试6","url":"/images/bluetooth.png"},
      {"name":"测试7","url":"/images/bluetooth.png"}]},
      {name:"分组05",children:[{"name":"测试0","url":"/images/bluetooth.png"},
      {"name":"测试1","url":"/images/bluetooth.png"},
      {"name":"测试2","url":"/images/bluetooth.png"},
      {"name":"测试3","url":"/images/bluetooth.png"},
      {"name":"测试4","url":"/images/bluetooth.png"},
      {"name":"测试5","url":"/images/bluetooth.png"},
      {"name":"测试6","url":"/images/bluetooth.png"},
      {"name":"测试7","url":"/images/bluetooth.png"}]},
    ],
    itemWidth: wx.getSystemInfoSync().windowWidth / 4,
    scrollAreaHeight:wx.getSystemInfoSync().windowHeight - 44,
    float:false,
    curSelectTab:0,
    scrollToItem:null,
    scrollTop: 0,//到顶部的距离
    listGroupHeight:0,
  },
  onReady:function() {
    this.cacluItemHeight();
  },
  scroll:function(e){
    console.log("scroll:",e);
    if(e.detail.scrollTop>=44){
      this.setData({
        float :true
      })
    }else if(e.detail.scrollTop<44) {
      this.setData({
        float :false
      })
    }
    let scrollTop = e.detail.scrollTop;
    let current =this.data.curSelectTab;
    if(scrollTop >=this.distance) {
      //页面向上滑动
      //列表当前可视区域最底部到顶部的距离 超过 当前列表选中项距顶部的高度(且没有下标越界),则更新tab栏
      if(current + 1 <this.heightArr.length && scrollTop >=this.heightArr[current]) {
        this.setData({
          curSelectTab: current + 1
        })
      }
    }else{
      //页面向下滑动
      //如果列表当前可视区域最顶部到顶部的距离 小于 当前列表选中的项距顶部的高度,则切换tab栏的选中项
      if(current - 1 >= 0 && scrollTop <this.heightArr[current - 1]) {
        this.setData({
          curSelectTab: current - 1
        })
      }
    }
    //更新到顶部的距离
    this.distance = scrollTop;
  },
  tabClick(e){
    this.setData({
      curSelectTab: e.currentTarget.dataset.index,
      scrollToItem:"v_"+e.currentTarget.dataset.index
    })
  },
  //计算每一个item高度
  cacluItemHeight() {
    let that =this;
    this.heightArr = [];
    let h = 0;
    const query = wx.createSelectorQuery();
    query.selectAll('.list-group-item').boundingClientRect()
    query.exec(function(res) {
      res[0].forEach((item) => {
        h += item.height;
        that.heightArr.push(h);
      })
      console.log(that.heightArr);
      that.setData({
        listGroupHeight: that.heightArr[that.heightArr.length - 1 ]
      })
    })
  },
})

在逻辑代码中最主要的有两个地方:

  1. cacluItemHeight:计算列表中item的高度数组,并将最终计算的结果保存在heightArr数组中。heightArr数组中的每一项的值是在前一项的基础之上进行累加。
  2. scroll:判断当前的滚动方向,根据滚动判断当前的方向,然后根据滚动的距离设置当前选择的tab。

基于以上内容,可以实现想要的滚动联动、切换tab联动效果。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号