react-native滑动吸顶效果的实现过程
前⾔
最近公司开发⽅向偏向移动端,于是就被调去做RN(react-native),体验还不错,当前有个需求是⾸页中间吸顶的效果,虽然已经很久没写样式了,不过这种常见样式应该是so-easy,没成想翻车了,⽹上搜索换了⼏个⽅案都不⾏,最后去github上复制封装好的库来实现,现在把翻车过程记录下来。
需求效果
翻车过程
第⼀种⽅案失败
⼀开始的思路是这样的,⼤众思路,我们需要监听页⾯的滚动状态,当页⾯滚动到要吸顶元素所处的位置的时候,我们设置它为固定定位,不过很遗憾,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模拟器⼀跑就⽆语了 ,效果很奇葩,⼿指滑动时不吸顶直接划上去隐藏掉⼤半,⼀松突然吸顶了。。。
见下图
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组件了,渲染的问题解决了,下⾯思路是动态设置吸顶组件的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],
});
<Animated.View
onLayout= { this._onLayout }
style = {
[
style,
{ transform: [{ translateY }] }
]}
>
{ children }
</Animated.View>
)
}
}
const styles = ate({
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 }) // 使⽤原⽣动画驱动absolute relative
}
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>
收尾
具体代码就是这样实现了,算是⽐较完美的⽅案,特别是照顾了性能,各位可以基于这个封装来实现更复杂的需求,原理⼤概就是这个原理了,在前端动画领域,⾃⼰确实也就刚⼊门⽔平,如有问题,请直接指出。
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论