React Native 自定义下拉刷新组件

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

针对猴急一些的同学,可以先在这个 Expo网站在线运行下demo看看效果

完整的代码,在 Github仓库

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

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

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

一步步实现自定义下拉

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

<View>
  <Animated.View><HeaderComponent /></Animated.View>
  <FlatList />
</View>

最外层的 View 的展示区域,和用户自己的 FlatList 完全一样。那么问题来了,我们的下拉刷新 HeaderComponent 在默认情况下,应该是不可见的,是在用户下拉过程中,逐渐的从上到下进入容器的可视区域。那就默认把 HeaderComponent 绝对定位到容器可视区域的外边吧,可是往上移动多大呢,这就需要用户告诉我们容器一个下拉组件的高度了,props.headerHeight ,到这一步,容器渲染出来的样式大概如下:

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

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

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

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

如果我们用容器的 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上:https://github.com/sophister/react-native-pull-to-refresh-custom

相关链接

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

推荐阅读更多精彩内容