想先引荐一下近期在写的一个React Native项目,名字叫
Gakki :是一个
Mastodon的第三方客户端 (Android App)
预览
写在前面
原本我也不想造这个轮子的,怎样没找到适宜的组件。只能本身上了~
思绪很清晰: 监听转动事宜,动态修正Header组件和Content组件的top值(固然,他们默许都是position:relative)。
接下来完成的时刻遇到了题目,我第一个版本是经由过程动态设置state来完成,即:
/**
* 每次转动时,从新设置headerTop的值
*/
onScroll = event =>{
const y = event.nativeEvent.contentOffset.y
if (y >= 270) return
// headerTop等于Header和Content的top款式对应的值
this.setState({
headerTop: y
})
}
如许虽然能完成,然则结果不好:显著能够看到在上滑的过程当中,Header组件一卡一卡地向上方挪动(一点都不流通)。
由于就只能另寻他法了:动画
React Native 供应了两个互补的动画体系:用于建立邃密的交互掌握的动画
Animated
和用于全局的规划动画
LayoutAnimation
(笔者注:此次没有用到它)
Animated 相干API引见
起首,这儿有一个简朴“逐步显现”动画的DEMO,须要你先看完(文档很简朴清楚明了且解释清晰,没必要Copy过来)。
在看懂了DEMO的基础上,我们还须要相识两个症结的API才完成完全的结果:
1. interpolate
插值函数。用来对差别范例的数值做映照处置惩罚。
固然,这是文档申明:
Each property can be run through an interpolation first. An interpolation maps input ranges to output ranges, typically using a linear interpolation but also supports easing functions. By default, it will extrapolate the curve beyond the ranges given, but you can also have it clamp the output value.
翻译:
每一个属性能够先经由插值处置惩罚。插值对输入局限和输出局限之间做一个映照,平常运用线性插值,但也支撑紧张函数。默许情况下,假如给定数据超出局限,他也能够自行推断出关于的曲线,但您也能够让它箝位输出值(P.S. 末了一句能够翻译毛病,由于没搞懂clamp value指的是什么, sigh…)
举个例子:
在完成一个图片扭转动画时,输入值只能是如许的:
this.state = {
rotate: new Animated.Value(0) // 初始化用到的动画变量
}
...
// 这么映照是由于style款式须要的是0deg如许的值,你给它0如许的值,它可不能平常事情。由于一定须要一个映照处置惩罚。
this.state.rotate.interpolate({ // 将0映照成0deg,1映照成360deg。固然中心的数据也是云云映照。
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
2. Animated.event
平常动画的输入值都是默许设定好的,比方前面DEMO中的逐步显现动画中的透明度:最先是0,末了是1。这是已写死了的。
但假如有些动画结果须要的不是写死的值,而是动态输入的呢,比方:手势(上滑、下滑,左滑,右滑…)、别的事宜。
那就用到了Animated.event
。
直接看一个将转动事宜的y值(转动条间隔顶部高度)和我们的动画变量绑定起来的例子:
// 这段代码示意:在转动事宜触发时,将event.nativeEvent.contentOffset.y 的值动态绑定到this.state.headerTop上
// 和最前面我经由过程this.setState动态设置的目标一样,但交给Animated.event做就不会形成视觉上的卡顿了。
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: { y: this.state.headerTop }
}
}
])}
关于API更多的申明请移步文档
完全代码
import React, { Component } from 'react'
import { StyleSheet, Text, View, Animated, FlatList } from 'react-native'
class List extends Component {
render() {
// 模仿列表数据
const mockData = [
'强盛',
'民主',
'文化',
'调和',
'自在',
'同等',
'公平',
'法治',
'爱国',
'敬业',
'诚信',
'和睦'
]
return (
<FlatList
onScroll={this.props.onScroll}
data={mockData}
renderItem={({ item }) => (
<View style={styles.list}>
<Text>{item}</Text>
</View>
)}
/>
)
}
}
export default class AnimatedScrollDemo extends Component {
constructor(props) {
super(props)
this.state = {
headerTop: new Animated.Value(0)
}
}
componentWillMount() {
// P.S. 270,217,280区间的映照是通知interpolate,一切大于270的值都映照成-50
// 如许就不会致使Header在上滑的过程当中一向向上滑动了
this.top = this.state.headerTop.interpolate({
inputRange: [0, 270, 271, 280],
outputRange: [0, -50, -50, -50]
})
this.animatedEvent = Animated.event([
{
nativeEvent: {
contentOffset: { y: this.state.headerTop }
}
}
])
}
render() {
return (
<View style={styles.container}>
<Animated.View style={{ top: this.top }}>
<View style={styles.header}>
<Text style={styles.text}>linshuirong.cn</Text>
</View>
</Animated.View>
{/* 在oHeader组件上移的同时,列表容器也须要同时向上挪动,须要注重。 */}
<Animated.View style={{ top: this.top }}>
<List onScroll={this.animatedEvent} />
</Animated.View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
height: 80,
backgroundColor: 'pink',
marginBottom: 1,
alignItems: 'center',
justifyContent: 'center',
color: 'white'
},
header: {
height: 50,
backgroundColor: '#3F51B5',
alignItems: 'center',
justifyContent: 'center'
},
text: {
color: 'white'
}
})