小程序自定义下拉刷新

3,601 阅读3分钟

自定义下拉刷新

微信小程序提供下拉刷新的api

onPullDownRefresh

  • 需要在app.json的window选项中或页面配置中开启enablePullDownRefresh。
  • 可以通过wx.startPullDownRefresh触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。
  • 当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。

诚然,这个api也有自身的限制,比如只能在Page()下调用,比如刷新动画,比如下拉距离。所以我们可以实现一个自定义下拉刷新组件

实现自定义下拉刷新组件

定义customPullDown组件

<!-- component/customPullDown/index.wxml -->
<!-- 设置两个插槽 -->
<view class="pull-down">
  <!-- 放置刷新动画 -->
  <slot name="refresh-animation"></slot>
  <!-- 放置内容 -->
  <slot name="content"></slot>
</view>
// component/customPullDown/index.js
Component({
	// 声明可以多个slot
	options: {
		multipleSlots: true
	},
})

页面使用

<!-- page/index.wxml -->
<view class="container">
  <custom-pull-down class="custom-pull-down">
    <view name="refresh-animation"></view>
    <view slot="content">
      <block wx:for="{{goodsList}}" wx:key="key">
        <view class="goods-item">
          <view >{{item.name}}</view>
        </view>
      </block>
    </view>
  </custom-pull-down>
</view>
// page/index.js
Page({
  data: {
    goodsList: Array.from({length: 10}, (item, index) => {
      return {
        key: `第${index+1}个`,
        name: `第${index+1}个`
      }
    })
  },
  onLoad() {
  },
})

初始化组件

利用scroll-view的特点

scroll-view 自定义下拉刷新可以结合 WXS 事件响应 开发交互动画

scroll-view提供的几个api

属性类型默认值说明
refresher-enabledbooleanfalse开启自定义下拉刷新
refresher-thresholdnumber45设置自定义下拉刷新阈值
refresher-default-stylestring"black"设置自定义下拉刷新默认样式,支持设置 blackwhitenone, none 表示不使用默认样式
refresher-backgroundstring"#FFF"设置自定义下拉刷新区域背景颜色
refresher-triggeredbooleanfalse设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
bindrefresherpullingeventhandle自定义下拉刷新控件被下拉
bindrefresherrefresheventhandle自定义下拉刷新被触发
bindrefresherrestoreeventhandle自定义下拉刷新被复位
bindrefresheraborteventhandle自定义下拉刷新被中止

修改组件

# component/customPullDown/index.wxml
<view class="pull-down">
  <slot name="refresh-animation"></slot>
-  <slot name="content"></slot>
+  <scroll-view
+    refresher-enabled
+    scroll-y="{{true}}"
+    scroll-with-animation
+    refresher-default-style="black"
+    class="scroll-box"
+  >
+    <slot name="content"></slot>
+  </scroll-view>
</view>

看下效果

第一版下拉刷新.gif 现在只是简单配置,所以刷新动画是默认的,并且会一直处于下拉刷新中的状态。

<view class="pull-down">
  <slot name="refresh-animation"></slot>
  <scroll-view
    refresher-enabled
    scroll-y="{{true}}"
    scroll-with-animation
    refresher-default-style="none"
+    refresher-threshold="{{refresherThreshold}}"
+    refresher-triggered="{{refresherTriggered}}"
    class="scroll-box"
  >
+    <!-- 自定义下拉刷新动画 -->
+    <view slot="refresher" class="custom-refresh-zone">
+      <view class="custom-refresh-zone-tips-loading">加载中</view>
+    </view>
    <slot name="content"></slot>
  </scroll-view>
</view>

通过slot="refresher"scroll-view插入一个插槽,可以实现自定义刷新样式

刷新样式自定义.gif

结合WXS实现交互效果

刷新动画分为三个阶段:继续下拉刷新->释放刷新->加载中。

下拉过程:

  1. 用户开始下拉的时候,自定义刷新区域展示【继续下拉刷新】
  2. 当下拉到一定距离changeBoundary时,变为【释放刷新】
  3. 此时松开手,变为【加载中】,触发bindrefresherrefresh
  4. 手动设置refresher-triggered为true,表示开始刷新状态,自定义刷新区域会一直停留
  5. 等到refresher-triggered设置为false时,表示结束刷新状态,自定义刷新区域会收起
<!-- component/customPullDown/index.wxml -->
<wxs module="pullDown" src="./pullDown.wxs"></wxs>
<view class="pull-down">
  <scroll-view
    refresher-enabled
    scroll-y
    scroll-with-animation
    refresher-default-style="none"
    refresher-triggered="{{refresherTriggered}}"
    bindrefresherpulling="{{pullDown.onContentPull}}"
    bindrefresherrestore="{{pullDown.onRestore}}"
    bindrefresherabort="{{pullDown.onAbort}}"
    bindrefresherrefresh="{{pullDown.onRefresh}}"
    bindscrolltolower="onReachBottom"
    class="scroll-box"
  >
    <!-- 自定义下拉刷新动画 -->
    <view slot="refresher" class="custom-refresh-zone" data-threshold="{{changeBoundary}}">
      <!-- 继续下拉刷新 -->
      <view class="refresh-before-trigger">
        <slot name="refresh-before-trigger"></slot>
      </view>
      <!-- 释放刷新 -->
      <view class="refresh-after-trigger">
        <slot name="refresh-after-trigger"></slot>
      </view>
      <!-- 加载中 -->
      <view class="refresh-loading">
        <slot name="refresh-loading"></slot>
      </view>
    </view>
    <!-- 内容插槽 -->
    <slot name="content"></slot>
  </scroll-view>
</view>
// component/customPullDown/pullDown.wxs
var refresherBefore = 'refresher-before'
var refresherAfter = 'refresher-after'

// 获取组件对象
function getComponent(name, selector) {
  return function(instance) {
    var state = instance.getState()
    return state[name] || (state[name] = instance.selectComponent(selector))
  }
}
var getCustomRefresher = getComponent('customRefresher', '.custom-refresh-zone')

module.exports = {
  onContentPull: function (event, ownerInstance) {
    var scrollY = event.detail.dy // 滚动距离

    // 根据滚动距离切换状态
    var customRefresher = getCustomRefresher(ownerInstance)
    var threshold = customRefresher.getDataset().threshold
    var isLargerThanTriggerThreshold = scrollY > threshold
    // 通过css类名控制展示
    customRefresher
      .addClass(isLargerThanTriggerThreshold ? refresherAfter : refresherBefore)
      .removeClass(isLargerThanTriggerThreshold ? refresherBefore : refresherAfter)
  },
  onRestore: function (event, ownerInstance) {
    console.log('onRestore 自定义下拉刷新被复位');
    ownerInstance.callMethod('onRefresherRestore', event)
  },
  onAbort: function (event, ownerInstance) {
    console.log('onRestore 自定义下拉刷新被中止');
    ownerInstance.callMethod('onRefresherAbort', event)
  },
  onRefresh: function (event, ownerInstance) {
    console.log('自定义下拉刷新被触发');
    var customRefresher = getCustomRefresher(ownerInstance)
    // 移除添加的类名,此时会展示加载中
    customRefresher.removeClass(refresherAfter, refresherBefore)
    ownerInstance.callMethod('onPullDownRefresh', event)
  },
}
/* component/customPullDown/index.wxs */
.custom-refresh-zone{
  width: 100%;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.refresh-loading{
  width: 100%;
}

.custom-refresh-zone .refresh-before-trigger,
.custom-refresh-zone .refresh-after-trigger{
  display: none;
}
.custom-refresh-zone.refresher-before .refresh-before-trigger,
.custom-refresh-zone.refresher-after .refresh-after-trigger{
  width: 100%;
  display: block;
}
.custom-refresh-zone.refresher-before .refresh-loading,
.custom-refresh-zone.refresher-after .refresh-loading{
  display: none;
}
// component/customPullDown/index.js
// ...
// 手动控制
onPullDownRefresh (event) {
  this.setData({
    refresherTriggered: true
  })
  setTimeout(() => {
    this.setData({
      refresherTriggered: false
    })
  }, 1000)
},

完整demo.gif

大功告成!

完整Demo

源码地址

参考