[wx]微信小程序自定义下拉刷新

需求:

在小程序内存在列表等形式的页面内增加下拉刷新功能,提高用户体验感,加强界面操作与交互性;

<a name="gXpSi"></a>

实现方法:

1、小程序提供的下拉刷新(无法自定义刷新动画)

  • 在页面设置内开启下拉(单独页面设置);
{
  "enablePullDownRefresh": true,
}
  • 使用onPullDownRefresh()监听用户下拉操作,实现刷新操作;
  • 也可以通过wx.startPullDownRefreshwx.stopPullDownRefresh触发和关闭页面下拉刷新;

可能遇到的问题:
1)下拉时没有出现刷新的点点动画
可能是因为设置的页面背景色与点点动画一致导致无法看到动画,可以在页面配置中配置背景文字颜色:

{
  "backgroundTextStyle": "dark"
}

2、scroll-view内refresher-enabled属性开启自定义刷新
基本库要求:2.10.1

refresher-enabled boolean false 开启自定义下拉刷新 2.10.1
refresher-threshold number 45 设置自定义下拉刷新阈值 2.10.1
refresher-default-style string "black" 设置自定义下拉刷新默认样式,支持设置 black/white/none, none 表示不使用默认样式 2.10.1
refresher-background string "#FFF" 设置自定义下拉刷新区域背景颜色 2.10.1
refresher-triggered boolean false 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发 2.10.1
bindrefresherpulling eventhandle 自定义下拉刷新控件被下拉 2.10.1
bindrefresherrefresh eventhandle 自定义下拉刷新被触发 2.10.1
bindrefresherrestore eventhandle 自定义下拉刷新被复位 2.10.1
bindrefresherabort eventhandle 自定义下拉刷新被中止 2.10.1

官方文档:scroll-view

3、原始scroll-view自定义下拉实现(为兼容2.10.1一下的下拉刷新
通过监听手指移动距离控制需要下拉模块的下拉距离,主要事件bindtouchstart,bindtouchmove和bindtouchend,bindtouchmove记录手指开始下拉时的起始位置,bindtouchmove计算下拉距离,bindtouchend判断并实现刷新方法。
我的自定义下拉组件(Taro框架)

import Taro, { Component, render } from "@tarojs/taro";
import { View, Image, ScrollView } from "@tarojs/components";
import { getGlobalData } from "../../state/global";
import util from "../../utils";
import "./index.less";

const rpx2px = util.rpx2px();
export default class Loading extends Component {
  config = {
    enablePullDownRefresh: false,
    disableScroll: true
  };
  constructor() {
    super(...arguments);
    this.state = {
      now_scroll_top: 0,
      moveStartPosition: 0, //开始位置
      moveDistance: 0, //移动距离
      moveRefreshDistance: rpx2px(136), //达到刷新的阈值,没有达到不进行刷新并回弹
      moveMaxDistance: rpx2px(220), //最大可滑动距离
      isRefreshMaxDown: false, //是否达到了最大距离, 用来判断是否要震动提示
      loading: false, //是否正在loading
      loading_scale: 0,
      // 2020.03.03 动态分成两个阶段,下拉时展示小鸭子浮动,放手后加载中鳄鱼
      pull_moving: false,
      back_top_num: ""
    };
  }

  componentWillReceiveProps(nextProps) {
    // 父组件触发刷新结束/取消刷新
    // console.log(nextProps, this.state.loading)
    if (nextProps.refreshLoading != this.state.loading) {
      if(nextProps.refreshLoading) {
        this.setState({
          loading: nextProps.refreshLoading
        });
      } else {
        this.stopPullLoading();
      }
    }
  }

  render() {
    let {
      now_scroll_top,
      moveDistance,
      loading_scale,
      loading,
      pull_moving,
      back_top_num
    } = this.state;
    let { autoHeight } = this.props;
    let sys_info = getGlobalData("system_info");
    return (
      <ScrollView
        className={["view-component component-pull-scroll", autoHeight ? "scroll-auto" : ""]}
        id="pull-scroll-view"
        style={{ height: autoHeight ? 'auto' : (sys_info.screenHeight - 80 + "px") }}
        scrollY={true}
        scrollTop={back_top_num}
        data-nowScrollTop={now_scroll_top}
        onTouchStart={this.pullTouchStart.bind(this)}
        onTouchMove={this.pullTouchMove.bind(this)}
        onTouchEnd={this.pullTouchEnd.bind(this)}
        onScroll={this.pullScroll.bind(this)}
        scrollWithAnimation
      >
        <View
          className="pull-view-container"
          style={{ transform: "translateY(" + moveDistance + "px)" }}
        >
          <View
            className="pull-refresh-box"
            style={{
              transform: "scale(" + loading_scale + ") translateY(-100%)"
            }}
          >
            {(pull_moving || loading) && (
              <Image
                className="loading-img"
                src="http://assets-cdn.lanqb.com/manual-box/mp/mp-pullLoading-duck.gif"
              ></Image>
            )}
          </View>
          <slot></slot>
        </View>
      </ScrollView>
    );
  }

  /**
   * 触摸事件开始
   * @param e
   */
  pullTouchStart(e) {
    let {
      moveDistance,
      moveStartPosition,
      loading,
      now_scroll_top
    } = this.state;
    if (loading || now_scroll_top >= 30) return;
    moveDistance = 0; //重置移动距离
    moveStartPosition = e.touches[0].clientY; //记录开始移动的位置
    this.setState({
      moveDistance,
      moveStartPosition
    });
  }
  /**
   * 触摸事件移动
   * @param e
   */
  pullTouchMove(e) {
    const _this = this;
    let {
      loading,
      moveDistance,
      moveStartPosition,
      moveMaxDistance,
      isRefreshMaxDown,
      loading_scale,
      moveRefreshDistance,
      pull_moving,
      now_scroll_top
    } = this.state;
    //如果正在loading,则不进行接下来的行为
    if (loading || now_scroll_top >= 30) return; 
    //当前scroll-view所在的滚动位置
    let nowScrollTop = e.currentTarget.dataset.nowscrolltop;

    //开始计算移动距离
    moveDistance = e.touches[0].clientY - moveStartPosition;

    //如果是往下滑动,则阻止接下来的行为
    if (moveDistance <= 0) {
      loading_scale = 0;
      // _this.stopPullLoading();
    } else {
      // if (nowScrollTop !== 0) {
      //   // 如果滚动高度 !== 0 则不进行任何操作
      // } else {
        if (moveDistance > moveMaxDistance) {
          //达到阈值
          // 显示刷新动画
          pull_moving = true;
          loading_scale = 1;
          loading = false;
          moveDistance = moveMaxDistance;
          //触发一次震动
          if (!isRefreshMaxDown) {
            isRefreshMaxDown = true;
            Taro.vibrateShort();
          }
        } else if (moveDistance < moveRefreshDistance) {
          loading_scale = 0;
          loading = false;
          pull_moving = false;
        } else {
          loading = false;
          pull_moving = true;
          loading_scale =
            moveDistance / rpx2px(136) > 1 ? 1 : moveDistance / rpx2px(136);
        }
      // }
    }
    this.setState({
      moveDistance,
      isRefreshMaxDown,
      loading_scale,
      pull_moving
    });
  }
  /**
   * 结束触摸事件
   */
  pullTouchEnd(e) {
    const _this = this;
    let {
      loading,
      moveDistance,
      moveRefreshDistance,
      loading_scale,
      moveMaxDistance,
      pull_moving,
      now_scroll_top
    } = this.state;
    if (loading || now_scroll_top >= 30) return;

    //当前scroll-view所在的滚动位置
    let nowScrollTop = e.currentTarget.dataset.nowscrolltop;

    //如果是往下滑动,则阻止接下来的行为
    if (moveDistance <= 0) {
      // return false
      loading_scale = 0;
      moveDistance = 0;
      loading = false;
      pull_moving = false;
    } else {
        if (moveDistance < moveRefreshDistance) {
          // 移动距离小于刷新阈值,不进行刷新
          loading_scale = 0;
          moveDistance = 0;
          loading = false;
          pull_moving = false;
          console.log("需要回弹");
        } else {
          // 开始刷新
          loading = true;
          pull_moving = false;
          _this.pullRefresh();
          if (moveDistance >= moveMaxDistance) {
            moveDistance = moveRefreshDistance;
            loading_scale =
              moveDistance / rpx2px(136) > 1 ? 1 : moveDistance / rpx2px(136);
          }
        }
      // }
    }
    this.setState({
      loading,
      //重置
      moveStartPosition: 0,
      isRefreshMaxDown: false,
      loading_scale,
      moveDistance,
      pull_moving
    });
  }
  pullRefresh() {
    const _this = this;
    this.props.onPullRefresh();
  }
  pullScroll(event) {
    let { scrollTop } = event.detail;
    this.setState({
      now_scroll_top: scrollTop,
      back_top_num: ""
    });
  }
  stopPullLoading() {
    const _this = this;
    setTimeout(function() {
      _this.setState({
        moveDistance: 0,
        moveStartPosition: 0,
        isRefreshMaxDown: false,
        pull_moving: false,
        back_top_num: 0,
        now_scroll_top: 0
      });
      setTimeout(function() {
        _this.setState({          
          loading_scale: 0,
          loading: false,
        })
      }, 1500)
    }, 2000)
  }
}

<a name="fEutw"></a>

部分问题:

1、与ios上橡皮筋效果冲突导致下拉无法触发自定义刷新
页面配置disabledScroll,禁止页面滚动,同时页面内的列表滚动需要自己再优化调整;

2、scroll-view的scroll问题
需要设定固定高度然后纵向滚动。[我都忘了是啥问题了……

3、页面下拉刷新结束后再滑动列表出现闪屏
在禁止页面橡皮筋效果后,如果页面内存在需滚动区域使用scroll-view效果比view更加流畅;
不设定固定高度不会发生闪屏但是页面滚动非常不流畅;

4、使用官方提供的scroll-view自定义的动画时,当scroll-view内容不足充满一屏时下拉出现问题
将scroll-view设置固定高度后,将其子元素的高度设置多一像素达到隐形撑满的效果。

<ScrollView
    className={["view-component component-pull-scroll", autoHeight ? "scroll-auto" : ""]}
    id="pull-scroll-view"
    style={{ height: (sys_info.screenHeight + "px") }}
>
    <View
        className="pull-view-container"
        style={{ height: (sys_info.screenHeight + 1 + "px") }}
    >...</View>
</ScrollView>

5、scroll-view内fixed元素问题
ios内scroll-view内fixed元素层级会出现问题,可能出现被遮挡的问题。

6、当页面内局部需要下拉刷新时可能导致内外两个滚动条问题
一个是页面滚动条一个是scroll-view滚动条,由于操作的时候触发的是scroll-view部分的滚动导致页面滚动无法进行从而影响页面其他操作。
Q5和Q6可以合并成一个问题,当页面需要一个吸顶操作时,即滑动距离超过阈值时导航条吸顶的功能,若scroll-view将整个页面包含就会出现Q5的问题,可能导致在ios内吸顶的导航栏无法显示,若scroll-view只包含需要刷新的部分则会出现Q6的两个滚动条的问题。
1)在页面未触发吸顶时禁止scroll-view模块下拉,触发后放开滚动,同时会导致无法下拉。
2)页面滚动触发,scroll-view模块可下拉,但是滚动区域无法滚动,且下拉动画只显示一次。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。