React-Native列表顶部随滑动渐变效果
概要
本篇文章的内容在于通过React-Native实现一个列表顶部随滑动渐变的效果。
一开始使用state来保存顶部需要变动样式的值,后来发现由于RN对state状态改变时的处理机制,导致页面呈现的效果不具实时性且不太流畅,所以改进使用了RN自己提供的Animated动画和ScrollView滑动组件。
顶部view区域颜色随滑动渐变
通过Animated自己附带的事件nativeEvent获取ScrollView的滑动距离
//声明滑动动画事件
this.animatedEvent = Animated.event(
[
{
nativeEvent: {
contentOffset: { y: this.state.navBackColorOffset } //将y值保存到state中背景色偏移距离的值上
}
}
]
)
通过state中保存的偏移距离navBackColorOffset,映射到动画中,定义输入区间和输出区间的值(设置useNativeDriver为true,使用原生驱动提升效率)。
//生成背景色动画输入输出区间
const navBackColorOffset = this.state.navBackColorOffset
this.backgroundAnimated = navBackColorOffset.interpolate({
inputRange: [0, this.changeNavHeight],
outputRange: ['#F2F740', '#FA5D4D'],
extrapolate: 'clamp', //阻止输出值超过outputRange
useNativeDriver: true,
})
当ScrollView开始滑动后,调用onScroll中绑定的this.animatedEvent方法,记录偏移量并保存到state中。
<ScrollView
style={{ flex:1, width:width }}
scrollEventThrottle={1}
onScroll={this.animatedEvent} //滑动监听事件
showsVerticalScrollIndicator={false}
>
...
</ScrollView>
将this.backgroundAnimated的输出赋值给顶部区域的背景色样式。
{/* 顶部背景色 */}
<Animated.View
style={{
backgroundColor:this.backgroundAnimated
}}
>
...
</Animated.View>
顶部图片透明度随滑动渐变
实现原理同上,只不过将View组件替换为Image组件显示,改变的是Image组件的透明度。
//声明滑动动画事件
this.animatedEvent = Animated.event(
[
{
nativeEvent: {
contentOffset: { y: this.state.navOpacityOffset } //将y值保存到state中透明度偏移距离的值上
}
}
]
)
通过state中保存的偏移距离navOpacityOffset,映射到动画中,定义输入区间和输出区间的值。
//生成透明度动画输入输出区间
const navOpacityOffset = this.state.navOpacityOffset
this.navOpacityAnimated = navOpacityOffset.interpolate({
inputRange: [0, this.changeNavHeight],
outputRange: [0, 1],
extrapolate: 'clamp', //阻止输出值超过outputRange
useNativeDriver: true,
})
将this.navOpacityAnimated的输出赋值给顶部区域的透明度。
{/* 顶部图片透明度 */}
<Animated.Image
style={{
opacity:this.navOpacityAnimated
}}
source={require('../../images/house/pic_topbg.png')} //这里替换成你自己的图片路径
/>
滑动监听
在源码中,你会发现在滑动事件animatedEvent中,有这样一个监听:
{
listener: e => {
//获取ScrollView在y轴上的偏移量
let y = e.nativeEvent.contentOffset.y;
if( y > this.changeNavHeight/2 ) {
//超过设定滑动距离1/2时,状态栏样式为浅色
StatusBar.setBarStyle('light-content');
}else{
//反之,状态栏样式为深色
StatusBar.setBarStyle('dark-content');
}
}
}
ScrollView滑动的过程中,会不断地调用onSroll事件绑定的方法,那么如果我们想在这个方法中,获取到一些其他的参数,或者是做一些事件或动作,该怎么去取值并处理呢?这里提供了listener监听,通过这个监听,我们可以拿到监听对象e,它包含的内容如下:
{
nativeEvent: {
contentInset: {bottom, left, right, top},
contentOffset: {x, y},
contentSize: {height, width},
layoutMeasurement: {height, width},
zoomScale
}
}
可以看出这些是ScrollView的布局参数等等,那么这样我们不仅可以拿到x,y的偏移量,还可以拿到内容布局的宽高等等,之后再进行一些处理。(在这里我只是加了个判断修改下状态栏的样式)
源码
import React, { Component } from 'react'
import {
Text,
Image,
View,
StyleSheet,
ScrollView,
StatusBar,
Platform,
Animated,
Dimensions,
} from 'react-native';
const { width, height } = Dimensions.get('window');
const isAndroid = Platform.OS === 'android';
const statusBarHeight = StatusBar.currentHeight;
export default class HeaderByScroll extends Component {
constructor(props) {
super(props)
this.changeNavHeight = 150; //决定改变导航栏样式的滑动距离
this.state={
navOpacityOffset: new Animated.Value(0), //改变导航背景图透明度的偏移距离
navBackColorOffset: new Animated.Value(0), //改变导航背景颜色的偏移距离
}
}
UNSAFE_componentWillMount(){
//设置状态栏颜色为透明(针对Android)
StatusBar.setBackgroundColor('transparent');
//声明滑动动画事件
this.animatedEvent = Animated.event(
[
{
nativeEvent: {
contentOffset: { y: this.state.navBackColorOffset } //将y值保存到state中背景色偏移距离的值上
// contentOffset: { y: this.state.navOpacityOffset } //将y值保存到state中透明度偏移距离的值上
}
}
],
{
listener: e => {
//获取ScrollView在y轴上的偏移量
let y = e.nativeEvent.contentOffset.y;
if( y > this.changeNavHeight/2 ) {
//超过设定滑动距离1/2时,状态栏样式为浅色
StatusBar.setBarStyle('light-content');
}else{
//反之,状态栏样式为深色
StatusBar.setBarStyle('dark-content');
}
}
}
)
// 生成背景色动画输入输出区间
const navBackColorOffset = this.state.navBackColorOffset
this.backgroundAnimated = navBackColorOffset.interpolate({
inputRange: [0, this.changeNavHeight],
outputRange: ['#F2F740', '#FA5D4D'],
extrapolate: 'clamp', //阻止输出值超过outputRange
useNativeDriver: true,
})
//生成透明度动画输入输出区间
// const navOpacityOffset = this.state.navOpacityOffset
// this.navOpacityAnimated = navOpacityOffset.interpolate({
// inputRange: [0, this.changeNavHeight],
// outputRange: [0, 1],
// extrapolate: 'clamp', //阻止输出值超过outputRange
// useNativeDriver: true,
// })
}
render(){
return(
<View style={{ flex:1, alignItems:'center' }}>
{/* 顶部背景色 */}
<Animated.View
style={[
styles.header,
{
alignItems:'center',
backgroundColor:this.backgroundAnimated
}
]}
>
<Text style={{fontSize:18,marginTop:isAndroid?statusBarHeight:44}}>滑动渐变导航栏</Text>
</Animated.View>
{/* 顶部图片透明度 */}
{/* <Animated.Image
style={[
styles.header,
{
opacity:this.navOpacityAnimated
}
]}
source={require('../../images/house/pic_topbg.png')} //这里替换成你自己的图片路径
/> */}
<ScrollView
style={{ flex:1, width:width }}
scrollEventThrottle={1}
onScroll={this.animatedEvent} //滑动监听事件
showsVerticalScrollIndicator={false}
>
<View style={[styles.itemView,{ height:300, backgroundColor:'#19BEB0' }]}>
<Text style={{fontSize:20}}>区域A</Text>
</View>
<View style={[styles.itemView,{ height:300, backgroundColor:'#F2F740' }]}>
<Text style={{fontSize:20}}>区域B</Text>
</View>
<View style={[styles.itemView,{ height:500, backgroundColor:'#FA5D4D' }]}>
<Text style={{fontSize:20}}>区域C</Text>
</View>
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
header: {
height: (isAndroid ? statusBarHeight : 44) + 44, //顶部适配距离,
width: width,
position: 'absolute',
zIndex: 10,
},
itemView: {
width: width,
alignItems: 'center',
justifyContent: 'center',
},
})
注:本文为作者原创,转载请注明作者及出处。