react-native滑动吸顶效果的实现过程


Posted in Javascript onJune 03, 2019

前言

最近公司开发方向偏向移动端,于是就被调去做RN(react-native),体验还不错,当前有个需求是首页中间吸顶的效果,虽然已经很久没写样式了,不过这种常见样式应该是so-easy,没成想翻车了,网上搜索换了几个方案都不行,最后去github上复制封装好的库来实现,现在把翻车过程记录下来。

需求效果

react-native滑动吸顶效果的实现过程

翻车过程

第一种方案 失败

一开始的思路是这样的,大众思路,我们需要监听页面的滚动状态,当页面滚动到要吸顶元素所处的位置的时候,我们设置它为固定定位,不过很遗憾,RN对于position属性只提供了两种布局方式:absolute和relative,既没有fixed也没有仍处于试验的api:sticky。尴尬了?

第二种方案 失败

不过也不慌,看网上有第二种方案,把图上第二 三块地方作为ScrollView,然后ScrollView滑动监听距离,把第一块的marginTop设为负值,但是这样第一部分不能滑动,不符合需求,pass

第三种方案 完全失败

从网上找到第三种方案,就是一二三部分作为ScrollView,

第一部分position设为absolute,剩下的不设置,默认是relative

第二部分(吸顶部分)marginTop设置(setState)为第一部分高度的state,

添加滑动onScroll事件=》滑动距离y等于第二部分marginTop的state,但是当滑动超过第一部分高度的时候把第二部分(吸顶部分)position设为absolute,并把其marginTop设为0,看起来不错,实际用ios模拟器一跑就无语了?,效果很奇葩,手指滑动时不吸顶直接划上去隐藏掉大半,一松突然吸顶了。。。

见下图

react-native滑动吸顶效果的实现过程

ios的系统,手指在屏幕上滚动时,onScroll一直在触发,如果里面有setState方法,也会不停执行并计算state,但是改变react的state是异步的,只要手指不离开屏幕,改变的state就无法生效(触发界面渲染)

实现方案

我最终意识到由于ios的机制,react的state机制不能满足需求,RN里面肯定有借助原生渲染的方式,于是github找了现成的代码实现之后,反过来进行研究,大家有RN丰富经验的也可以直接看最下面代码?

RN的Animator

RN的Animator动画库旨在解决动画问题,由于js桥接过程,动画通常不能很好展现,最好是把动画的 数据 和 变化方法 一次性发给原生,由原生进行处理,这就是Animator库的核心作用。

记得原来RN的动画一直被吐槽,不过现在效果还挺不错的,可能与近年来手机硬件提升也越来越大也有关系吧。

简单用法

由于Animator内部封装了这四个组件,所以默认可以导出<Animator.View/>,<Animator.Text/>,<Animator.Image/>,<Animator.ScrollView/>

在这几个组件里面想做一些动画处理,数据方面也是react的state,但是赋值要给Animated.Value,如下?

this.state = {
  scrollY: new Animated.Value(0)
}

这里虽然使用的还是原生state,但是经过Animated处理,渲染机制完全不一样了

简单原理

经过Animator包装后的组件,会遍历传入的props和自身的state,查找是否有Animated.Value的实例,并绑定进相应的原生操作。

props和自身的state变化时,将Animated.Value值逐个转化为普通数值,再交给原生进行渲染,但是值得注意的是,这里并不会触发react 的 render,更不会有什么domdiff ,是一种特殊处理,类似于Animated.Value改变时每次的shouldUpdateComponent返回都是false(毫秒级的渲染react性能扛不住),shouldUpdateComponent函数里面判断Animated.Value,然后会把数据变化发给原生组件

完整的介绍请移步中文官网Animator库介绍

实现思路

既然用了Animator组件了,渲染的问题解决了,下面思路是动态设置吸顶组件的translateY属性。style:{ transform: [{ translateY:translateY }] }

  • 当向下滑动时,不管它
  • 向上滑,但是当头部还没有完全隐藏时,也不管它
  • 向上滑,头部完全不见了,这时向上再滑一点,那么他的translateY就应该 = 上划总距离 - 头部高度,这样越往上滑,把吸顶组件使劲往下推,这样吸顶组件就牢牢固定在顶部了

下面利用插值来实现

const translateY = ScrollY.interpolate({
  inputRange: [-1, 0, headerHeight, headerHeight + 1],
  outputRange: [0, 0, 0, 1],
});

插值interpolate略难理解,需要一点基础,这里再细说起来这篇文章就太长了官网介绍,

如果还不懂可以去网上找找这方面的资料

实现源码

实现的图中第二部分吸顶功能的核心代码

import * as React from 'react';
import { StyleSheet, Animated } from "react-native";

/**
 * 滑动吸顶效果组件
 * @export
 * @class StickyHeader
 */
export default class StickyHeader extends React.Component{

  static defaultProps = {
    stickyHeaderY: -1,
    stickyScrollY: new Animated.Value(0)
  }
  
  constructor(props) {
    super(props);
    this.state = {
      stickyLayoutY: 0,
    };
  }
  // 兼容代码,防止没有传头部高度
  _onLayout = (event) => {
    this.setState({
      stickyLayoutY: event.nativeEvent.layout.y,
    });
  }

  render() {
    const { stickyHeaderY, stickyScrollY, children, style } = this.props
    const { stickyLayoutY } = this.state
    let y = stickyHeaderY != -1 ? stickyHeaderY : stickyLayoutY;
    const translateY = stickyScrollY.interpolate({
      inputRange: [-1, 0, y, y + 1],
      outputRange: [0, 0, 0, 1],
    });
    return (
      <Animated.View
        onLayout= { this._onLayout }
        style = {
          [
            style,
            styles.container,
            { transform: [{ translateY }] }
          ]}
      >

      { children }

      </Animated.View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    zIndex: 100
  },
});

页面里实际用法如下

// 在页面constructor里声明state
this.state = {
  scrollY: new Animated.Value(0),
  headHeight:-1
};
<Animated.ScrollView 
  style={{ flex: 1 }}
  onScroll={
    Animated.event(
      [{
        nativeEvent: { contentOffset: { y: this.state.scrollY } } // 记录滑动距离
      }],
      { useNativeDriver: true }) // 使用原生动画驱动
  }
  scrollEventThrottle={1}
>

  <View onLayout={(e) => {
    let { height } = e.nativeEvent.layout;
    this.setState({ headHeight: height }); // 给头部高度赋值
  }}>
    // 里面放入第一部分组件
  </View>
  
  <StickyHeader
    stickyHeaderY={this.state.headHeight} // 把头部高度传入
    stickyScrollY={this.state.scrollY}  // 把滑动距离传入
  >
    // 里面放入第二部分组件
  </StickyHeader>
  
  // 这是第三部分的列表组件
  <FlatList
    data={this.state.dataSource}
    renderItem={({item}) => this._createListItem(item)}
  />
  
</Animated.ScrollView>

收尾

具体代码就是这样实现了,算是比较完美的方案,特别是照顾了性能,各位可以基于这个封装来实现更复杂的需求,原理大概就是这个原理了,在前端动画领域,自己确实也就刚入门水平,如有问题,请直接指出。

另外,这是我找的那个 组件 github的代码地址:https://github.com/jiasongs/react-native-stickyheader,原地址附上,建议如果项目用了给人家一个star

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中的比较操作符&gt;、=、
Dec 31 Javascript
js实现checkbox全选、不选与反选的方法
Feb 09 Javascript
JavaScript三元运算符的多种使用技巧
Apr 16 Javascript
深入剖析javascript中的exec与match方法
May 18 Javascript
浅谈js script标签中的预解析
Dec 30 Javascript
js实现横向拖拽导航条功能
Feb 17 Javascript
使用rollup打包JS的方法步骤
Dec 05 Javascript
微信小程序实现动态获取元素宽高的方法分析
Dec 10 Javascript
实现Vue的markdown文档可以在线运行的方法示例
Dec 11 Javascript
vue基础之data存储数据及v-for循环用法示例
Mar 08 Javascript
js中关于Blob对象的介绍与使用
Nov 29 Javascript
Angular8 实现table表格表头固定效果
Jan 03 Javascript
vue-cli 3 全局过滤器的实例代码详解
Jun 03 #Javascript
vue2之简易的pc端短信验证码的问题及处理方法
Jun 03 #Javascript
使用RxJS更优雅地进行定时请求详析
Jun 02 #Javascript
Vue CLI3基础学习之pages构建多页应用
Jun 02 #Javascript
Vue基础学习之项目整合及优化
Jun 02 #Javascript
JavaScript判断对象和数组的两种方法
May 31 #Javascript
vue中node_modules中第三方模块的修改使用详解
May 31 #Javascript
You might like
解析phpstorm + xdebug 远程断点调试
2013/06/20 PHP
thinkphp项目部署到Linux服务器上报错“模板不存在”如何解决
2016/04/27 PHP
php中数组最简单的使用方法
2020/12/27 PHP
php并发加锁问题分析与设计代码实例讲解
2021/02/26 PHP
javascript转换日期字符串为Date日期对象的方法
2015/02/13 Javascript
jQuery选择器源码解读(五):tokenize的解析过程
2015/03/31 Javascript
JavaScript保存并运算页面中数字类型变量的写法
2015/07/06 Javascript
jQuery检测某个元素是否存在代码分享
2015/07/09 Javascript
谈谈encodeURI和encodeURIComponent以及escape的区别与应用
2015/11/24 Javascript
jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点
2015/12/01 Javascript
给before和after伪元素设置js效果的方法
2015/12/04 Javascript
分享我对JS插件开发的一些感想和心得
2016/02/04 Javascript
AngularJS 中的指令实践开发指南(一)
2016/03/20 Javascript
微信小程序 登陆流程详细介绍
2017/01/17 Javascript
原生js实现图片放大缩小计时器效果
2017/01/20 Javascript
Vue常用指令V-model用法
2017/03/08 Javascript
javascript实现的图片预览功能
2017/03/25 Javascript
详解Angular中的自定义服务Service、Provider以及Factory
2017/04/22 Javascript
微信小程序商品详情页规格属性选择示例代码
2017/10/30 Javascript
浅析Vue下的components模板使用及应用
2019/11/27 Javascript
vue父子模板传值问题解决方法案例分析
2020/02/26 Javascript
[44:26]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#4EG VS Fnatic第二局
2016/03/03 DOTA
[03:54]Ehome出征西雅图 回顾2016国际邀请赛晋级之路
2016/08/02 DOTA
批量获取及验证HTTP代理的Python脚本
2017/04/23 Python
Django3.0 异步通信初体验(小结)
2019/12/04 Python
Python中SQLite如何使用
2020/05/27 Python
SISLEY希思黎官方旗舰店:享誉全球的奢华植物美容品牌
2018/04/25 全球购物
武汉英思工程科技有限公司&ndash;ORACLE面试测试题目
2012/04/30 面试题
开展党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
房屋买卖授权委托书
2014/09/27 职场文书
村党支部书记四风问题个人对照检查材料思想汇报
2014/10/06 职场文书
2015年宣传思想工作总结
2015/05/22 职场文书
集结号观后感
2015/06/08 职场文书
爱国主义电影观后感
2015/06/18 职场文书
2019年暑期安全广播稿!
2019/07/03 职场文书
SQL SERVER存储过程用法详解
2022/02/24 SQL Server