React Native 自定义下拉刷新组件

React Native 自定义下拉刷新组件 PullToRefresh

针对猴急一些的同窗,能够先在这个 Expo网站在线运行下demo看看效果javascript

完整的代码,在 Github仓库html

下拉刷新,是一个很常见的交互方式。React-Native(如下简称RN)内置的 FlatList 是支持下拉刷新组件的,经过设置 refreshControl 属性便可。一般咱们不只仅须要定制下拉组件,还须要在下拉过程当中,下拉组件执行一些动画,好比在咱们场景下,公司logo会随着下拉的幅度,不一样的笔画仍是显现出颜色。这就须要咱们的下拉组件,知道当前下拉的幅度,以此来计算咱们动画执行的进度。显然,RN官方的 refreshControl并不能知足咱们的需求。前端

看到有两个已经存在的开源包 react-native-pull-refreshreact-native-ptr-control ,基本都有2年左右历史了,并且我也确实没看懂,为何要用到 两个 ScrollView 嵌套来实现。java

直观上来看,我应该只须要有一个 ScrollView 就能够了,我监听下拉距离,从新render自定义的下拉组件。嗯,按照这个思路,尝试撸一个试试。node

一步步实现自定义下拉

首先,咱们提供的是一个容器(命名为 PullToRefresh 吧 ),内部是用户经过 children 传进来的 FlatList,这样也方便用户修改,在须要自定义下拉刷新的场景下,用咱们这个容器把已经存在的 FlatList 包起来就能够了,改动也挺小。固然,由于是自定义下拉刷新header,确定还须要用户把自定义的下拉刷新header组件传进来,就命名为 props.HeaderComponent 吧,到这一步,咱们容器内render出来的DOM结构,大概是这样的:react

<View>
  <Animated.View><HeaderComponent /></Animated.View> <FlatList /> </View>
复制代码

最外层的 View 的展现区域,和用户本身的 FlatList 彻底同样。那么问题来了,咱们的下拉刷新 HeaderComponent 在默认状况下,应该是不可见的,是在用户下拉过程当中,逐渐的从上到下进入容器的可视区域。那就默认把 HeaderComponent 绝对定位到容器可视区域的外边吧,但是往上移动多大呢,这就须要用户告诉咱们容器一个下拉组件的高度了,props.headerHeight ,到这一步,容器渲染出来的样式大概以下:git

<View>
  <Animated.View style={{position: 'absolute', top: - this.props.headerHeight}}> <HeaderComponent /> </Animated.View> <FlatList /> </View>
复制代码

完成了初始的DOM结构样式,接下来容器下拉时机的问题。github

首先,何时用户下拉,是触发咱们容器的下拉操做,而不是内部的 FlatList 的默认下拉呢?这个好像比较直接,当内部的 FlatList 已经下拉到顶部,不能再继续下拉时,用户的下拉动做,就应该触发容器的下拉。那么,咱们就须要知道内部的 FlatList 的当前下拉位置,这能够经过 FlatList 的 onScroll 属性来获取当前 FlatList 的滚动距离。小程序

什么时机触发容器的下拉肯定了,那在容器下拉过程当中,咱们须要更新哪些组件呢?1) 自定义header组件确定要更新,将最新的下拉距离传给header组件。2) 若是只是将header组件往下移动,咱们的 FlatList 不动,那么自定义header会遮挡住 FlatList 的内容,这不是咱们想要的;所以,在容器下拉过程当中,内部的 FlatList 位置也须要响应的往下移动。react-native

若是咱们用容器的 state.containerTop 这个 Animated.Value 来保存当前容器下拉的距离,那么目前咱们容器render的DOM结果大概以下:

const headerStyle = {
  position: 'absolute',
  left: 0,
  width: '100%',
  top: -this.props.headerHeight,
  transform: [{ translateY: this.state.containerTop }],
};
<View>
  <Animated.View style={[{ flex: 1, transform: [{ translateY: this.state.containerTop }] }]}>
  	<FlatList />
  </Animated.View>
  <Animated.View style={headerStyle}>
  	<HeaderComponent />
  </Animated.View>
</View>
复制代码

这样,基本就完成了容器下拉过程当中,自定义header和内部的FlatList同步下拉了。

下拉动做实现了,那下拉到什么位置,能够触发刷新呢?这就须要用户再传递一个触发刷新的下拉距离,就叫 props.refreshTriggerHeight 吧,当用户松开时,若是当前下拉距离 >= props.refreshTriggerHeight ,就会调用用户传入的刷新函数 props.onRefresh 。一般,用户若是下拉的距离比较大,松开手指时触发了刷新动做,这时候会整个组件会先回跳到一个刷新中的位置,这个位置,用户能够经过 props.refreshingHoldHeight 来指定。props.refreshTriggerHeightprops.refreshingHoldHeight 都是可选的,若是用户不传,默认为 props.headerHeight

One More Thing

上面其实还省略了一些工做,最重要的,就是在容器下拉过程当中,怎么把下拉距离(下拉进度)传给用户的自定义 HeaderComponent ?上面容器上的 state.containerTop 其实就是当前容器下拉距离,只不过这是一个 Animated.Value ,咱们 不能 读取到它当前的值。所以,我在容器上添加了一个 实例属性 this.containerTranslateY 来保存当前容器下拉的距离,咱们会监听 state.containerTop 值的变化,在回调函数里,修改 this.containerTranslateY

等等!!containerTranslateY为何没有放到容器的 state 上呢?不该该是 this.state.containerTranslateY 么??嗯,刚开始我确实是放在 state 上的,而后在用户下拉容器过程当中,经过在容器上 setState,触发容器从新render,而后把 containerTranslateY 传递过header。可是,这样经过容器上 setState 触发header更新的方式,在我测试中,发现页面会比较卡顿。所以,在用户下拉容器过程当中,并没有去修改容器的 state ,而是经过 方法调用 的命令方式,将用户当前下拉距离传给了header组件。这里可能还能够怎么优化一下吧。I'm not sure.

所以,用户的自定义header组件,必须 暴露一个实例方法 setProgress 来接收容器下拉过程当中的一些参数,目前这个方法的签名是这样的:

// pullDistance 表示容器下拉的距离;percent 表明下拉的进度,[0, 1]
setProgress({pullDistance, percent}){}
复制代码

完整的 header 组件demo,请参考 expo上的运行demo

The End

最后,据说,微交互动画,使用 lottie 和 RN 更配哦。

原本想尝试用 AE 作一个公司logo的 lottie 动画的,奈何没hold住……

完整的代码在github上:github.com/sophister/r…

相关连接

广告时间

人人贷大端技术博客中心

最后广而告之。 欢迎访问 人人贷大前端技术博客中心

里面有关 nodejs react react native 小程序 前端工程化等相关的技术文章陆续更新中,欢迎访问和吐槽~

相关文章
相关标签/搜索