一、简介
React native中实现动画是依赖的Animated
库,主要侧重于输入和输出之间的声明性关系,以及两者之间的可配置变换,此外还提供了简单的 start/stop
方法来控制基于时间的动画执行。
二、与CSS 动画的区别
CSS实现动画一般有两种,第一种是@keyframes
结合animation
实现,另外一种是用transition
实现;
而React Native中则是创建一个Animated.Value
,这是一个用于控制动画的值,然后使用Animated
中的动画函数(如:timing
)或者类似ScrollView
的onScroll
函数去更改这个值,然后可以使用这个值赋给对应的Animated
组件style
中的trasnform
属性。
三、Animated库
Animated
库中主要提供三部分内容:
Animated.Value
- 驱动动画的一维标量值,;Animated.timing
等 - 动画函数,还有loop
、add
等用于计算、合成动画,event
用于处理手势和其他事件(如:onScroll
),start
、stop
、reset
用于控制动画;Animated.Component
- 动画组件,基础组件的style
是没有transform
属性的,可以用createAnimatedComponent
方法创建动画组件或者用Animted.View
、Animated.Image
等Animated
提供的动画组件;
四、例子
1. 嵌套滚动
有一些像博客内容的场景,最上面是封面标题,占住大量空间,下面是文章主体,在滚动的过程中,封面、标题被遮住或高度缩小以将更大的空间留给内容主体,看上去像嵌套滚动:
import { Animated, StyleSheet, Text, View } from 'react-native';
import React, { useState } from 'react';
const IMAGE_HEIGHT = 200;
const MIN_TITLE_HEIGHT = 80;
export default function () {
const data: number[] = new Array(10).fill(0);
const [scrollY] = useState(new Animated.Value(0));
const imageTranslateY = scrollY.interpolate({
inputRange: [0, IMAGE_HEIGHT - MIN_TITLE_HEIGHT],
outputRange: [0, IMAGE_HEIGHT - MIN_TITLE_HEIGHT],
extrapolate: 'clamp'
});
const headertranslateY = scrollY.interpolate({
inputRange: [0, IMAGE_HEIGHT - MIN_TITLE_HEIGHT],
outputRange: [0, MIN_TITLE_HEIGHT - IMAGE_HEIGHT],
extrapolate: 'clamp'
});
return (
<View style={styles.fill}>
<Animated.FlatList
style={[styles.fill]}
data={data}
renderItem={({ index }) => {
return (
<View style={styles.block}>
<Text style={styles.text}>{index}</Text>
</View>
);
}}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
useNativeDriver: true
})}
ListHeaderComponent={
<Animated.View style={[styles.header, { transform: [{ translateY: headertranslateY }] }]}>
<Animated.Image
style={[styles.image, { transform: [{ translateY: imageTranslateY }] }]}
source={require('~/assets/image/zelda.jpg')}
/>
</Animated.View>
}
stickyHeaderIndices={[0]}
/>
</View>
);
}
const styles = StyleSheet.create({
fill: {
flex: 1
},
header: {
width: '100%',
height: IMAGE_HEIGHT,
overflow: 'hidden',
backgroundColor: 'rgba(0, 0, 0, 0.3)'
},
image: {
width: '100%',
height: IMAGE_HEIGHT,
resizeMode: 'stretch'
},
block: {
width: '100%',
height: 100,
borderWidth: 1,
borderColor: '#000',
justifyContent: 'center',
alignItems: 'center'
},
text: {
fontSize: 36,
fontWeight: 'bold'
}
});
效果如下:
关键点有两个:
- 使用
Animated.event
将FlatList
的scrollY
映射到创建的Animated.Value
; - 再利用这个变量创建出映射值结合transform,使得滚动时,header整体向上移动,image往反方向移动,结合
overflow: 'hidden'
创造出一种图片已知在列表下没有移动位置的效果;
另外推荐另外一篇文章React Native ScrollView animated header,以上代码就是看完这篇文章之后写出来的,但也有不一样的地方,比如文章中使用的ScrollView,且Header是用的absolute布局结合paddingTop实现的,也是一个不错的案例;
2. 扫码loop效果
像微信的扫码会有一条扫描线,这个例子就是实现类似的扫描线的效果,是一个比较简单的组合动画:
import { Animated, Dimensions, Easing, StyleSheet, View } from 'react-native';
import { useState, useEffect } from 'react';
export default function () {
const [scanLightY] = useState(new Animated.Value(0));
const windowHeight = Dimensions.get('window').height;
useEffect(() => {
Animated.loop(
Animated.timing(scanLightY, {
toValue: windowHeight,
easing: Easing.inOut(Easing.quad),
duration: 2000,
useNativeDriver: true
})
).start();
}, [])
return (
<View style={{flex: 1}}>
<Animated.Image
style={[
styles.scanLight,
{
transform: [
{
translateY: scanLightY
}
]
}
]}
source={require('~/assets/image/scan-light.png')}
/>
</View>
)
}
const styles = StyleSheet.create({
scanLight: {
position: 'absolute',
width: '100%',
resizeMode: 'stretch'
}
});
效果如下: