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 相关文章推荐
Apply an AutoFormat to an Excel Spreadsheet
Jun 12 Javascript
jquery获取ASP.NET服务器端控件dropdownlist和radiobuttonlist生成客户端HTML标签后的value和text值
Jun 28 Javascript
到处都是jQuery选择器的年代 不了解它们的性能,行吗
Jun 18 Javascript
Javascript中arguments用法实例分析
Jun 13 Javascript
JS实现弹性漂浮效果的广告代码
Sep 02 Javascript
JS获取地址栏参数的两种方法(简单实用)
Jun 14 Javascript
Angular 页面跳转时传参问题
Aug 01 Javascript
Node.js学习入门
Jan 03 Javascript
js放到head中失效的原因与解决方法
Mar 07 Javascript
在JS中如何把毫秒转换成规定的日期时间格式实例
May 11 Javascript
详解vue 在移动端体验上的优化解决方案
May 20 Javascript
Vue.js中v-for指令的用法介绍
Mar 13 Vue.js
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
德生9700DX电路分析
2021/03/02 无线电
PHP 5.0对象模型深度探索之属性和方法
2008/03/27 PHP
treepanel动态加载数据实现代码
2012/12/15 Javascript
jQuery动画出现连续触发、滞后反复执行的解决方法
2015/01/28 Javascript
用javascript实现自动输出网页文本
2015/07/30 Javascript
javascript实现鼠标移到Image上方时显示文字效果的方法
2015/08/07 Javascript
Js删除数组中某一项或几项的几种方法(推荐)
2016/07/27 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
2016/07/28 Javascript
利用yarn实现一个webpack+react种子
2016/10/25 Javascript
servlet+jquery实现文件上传进度条示例代码
2017/01/25 Javascript
selenium 与 chrome 进行qq登录并发邮件操作实例详解
2017/04/06 Javascript
微信小程序实战之登录页面制作(5)
2020/03/30 Javascript
Angular 2 ngForm中的ngModel、[ngModel]和[(ngModel)]的写法
2017/06/29 Javascript
jQuery实现广告条滚动效果
2017/08/22 jQuery
nuxt框架中路由鉴权之Koa和Session的用法
2018/05/09 Javascript
vue项目使用axios发送请求让ajax请求头部携带cookie的方法
2018/09/26 Javascript
深入理解js A*寻路算法原理与具体实现过程
2018/12/13 Javascript
Python入门_浅谈数据结构的4种基本类型
2017/05/16 Python
Python使用微信SDK实现的微信支付功能示例
2017/06/30 Python
django中静态文件配置static的方法
2018/05/20 Python
PyQt5重写QComboBox的鼠标点击事件方法
2019/06/25 Python
python中几种自动微分库解析
2019/08/29 Python
使用python+whoosh实现全文检索
2019/12/09 Python
pytorch-RNN进行回归曲线预测方式
2020/01/14 Python
Django 自定义权限管理系统详解(通过中间件认证)
2020/03/11 Python
python主要用于哪些方向
2020/07/05 Python
巴西葡萄酒商店:Divvino
2020/02/22 全球购物
仓库组长岗位职责
2014/01/29 职场文书
水利水电建筑施工应届生求职信
2014/07/04 职场文书
新闻传播专业求职信
2014/07/22 职场文书
群众路线教育实践活动整改方案(个人版)
2014/10/25 职场文书
优秀共青团员事迹材料
2014/12/25 职场文书
房产遗嘱范本
2015/08/06 职场文书
如何做好工作总结!
2019/04/10 职场文书
vue3中的组件间通信
2021/03/31 Vue.js
Oracle数据库中通用的函数实例详解
2022/03/25 Oracle