另外,我在Github上建立了1个仓库来搜集优秀的React Native库和优秀的博客等
ReactNativeMaterials
目前,React Native的版本是0.28,主要的动画分为两大类
目前React native的release速度还是比较快的,每隔2周左右就release1次。
本文默许读者已
react-native init Demo --verbose
初始化了1个Demo项目1个最基本的Animated创建进程以下
Animated.Value
,设置初始值,比如1个视图的opacity
属性,最开始设置Animated.Value(0)
,来表示动画的开始时候,视图是全透明的。Animated.timing
来创建自动的动画,或使用Animated.event
来根据手势,触摸,Scroll的动态更新动画的状态(本文会侧重讲授Animated.timing
)Animated.timeing.start()
开始动画基于上述的原理,我们来实现第1个动画。
创建1个Demo工程的时候,运行后,摹拟器截图应当是酱紫的。
然后,只保存第1行文字,然后我们给这个默许的视图创建fade in动画,效果以下
代码
class Demo extends React.Component {
state: { //可以不写,我这里写是为了去除flow正告
fadeAnim: Object,
};
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), //设置初始值
};
}
componentDidMount() {
Animated.timing(
this.state.fadeAnim,//初始值
{toValue: 1}//结束值
).start();//开始
}
render() {
return (
<View style={styles.container}>
<Animated.Text style={{opacity: this.state.fadeAnim}}>//绑定到属性
Welcome to React Native!
</Animated.Text>
</View>
);
}
}
所以说,简单的动画就是用Animated.Value
指定初始值,然后在Animated.timing
中设置结束值,其他的交给React native让它自动创建,我们只需要调用start
开始动画便可。
在当前版本0.27种,可动画的视图包括
static decay(value, config)
阻尼,将1个值根据阻尼系数动画到 0static timing(value, config
根据时间函数来处理,常见的比如线性,加速开始减速结束等等,支持自定义时间函数static spring(value, config)
弹性动画static add(a, b)
将两个Animated.value
相加,返回1个新的static multiply(a, b)
将两个Animated.value
相乘,返回1个新的static modulo(a, modulus)
,将a对modulus取余,类似操作符%static delay(time)
延迟1段时间static sequence(animations)
顺次开始1组动画,后1个在前1个结束后才会开始,如果其中1个动画中途停止,则全部动画组停止static parallel(animations, config?)
,同时开始1组动画,默许1个动画中途停止,则全都停止。可以通过设置stopTogether
来重写这1特性static stagger(time, animations)
,1组动画可以同时履行,但是会依照延迟顺次开始static event(argMapping, config?)
,利用手势,Scroll来手动控制动画的状态static createAnimatedComponent(Component)
,自定义的让某1个Component支持动画Value
,类型是AnimatedValue
,驱动基本动画AnimatedValueXY
,类型是AnimatedValueXY
,驱动2维动画1个AnimatedValue1次可以驱动多个可动画属性,但是1个AnimatedValue1次只能由1个机制驱动。比如,1个Value可以同时动画View的透明度和位置,但是1个Value1次只能采取线性时间函数
constructor(value)
构造器setValue(value)
直接设置值,会致使动画终止setOffset(offset)
设置当前的偏移量flattenOffset()
将偏移量合并到最初值中,并把偏移量设为0,addListener(callback) ,removeListener(id),removeAllListeners()
,增加1个异步的动画监听者stopAnimation(callback?)
终止动画,并在动画结束后履行callbackinterpolate(config)
插值,在更新可动画属性前用插值函数对当前值进行变换animate(animation, callback)
通常在React Native内部使用stopTracking(),track(tracking)
通常在React Native内部使用和AnimatedValue类似,用在2维动画,使用起来和AnimatedValue类似,这里不在介绍,这里是文档。
有了上文的知识支持,我们可以设计并实现1个更加复杂的动画了。
效果
代码(省略了import和style)
class Demo extends React.Component {
state: {
fadeAnim: Animated,
currentAlpha:number,
};
constructor(props) {
super(props);
this.state = {//设置初值
currentAlpha: 1.0,//标志位,记录当前value
fadeAnim: new Animated.Value(1.0)
};
}
startAnimation(){
this.state.currentAlpha = this.state.currentAlpha == 1.0?0.0:1.0;
Animated.timing(
this.state.fadeAnim,
{toValue: this.state.currentAlpha}
).start();
}
render() {
return (
<View style={styles.container}>
<Animated.Text style={{opacity: this.state.fadeAnim, //透明度动画
transform: [//transform动画
{
translateY: this.state.fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [60, 0] //线性插值,0对应60,0.6对应30,1对应0
}),
},
{
scale:this.state.fadeAnim
},
],
}}>
Welcome to React Native!
</Animated.Text>
<TouchableOpacity onPress = {()=> this.startAnimation()} style={styles.button}>
<Text>Start Animation</Text>
</TouchableOpacity>
</View>
);
}
}
通过上文的讲授,相信读者已对如何用Animated创建动画有了最基本的认识。而有些时候,我们需要根据Scroll或手势来手动的控制动画的进程。这就是我接下来要讲的。
手动控制动画的核心是Animated.event
,
这里的Aniamted.event的输入是1个数组,用来做数据绑定
比如,
ScrollView中
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x绑定给this.state.xOffset
)}
Pan手势中
onPanResponderMove: Animated.event([
null,//疏忽native event
{dx: this.state.pan.x, dy: this.state.pan.y},//dx,dy分别绑定this.state.pan.x和this.state.pan.y
])
目标效果 - 随着ScrollView的相左滑动,最左侧的1个Image透明度逐步下降为0
核心代码
var deviceHeight = require('Dimensions').get('window').height;
var deviceWidth = require('Dimensions').get('window').width;
class Demo extends React.Component {
state: {
xOffset: Animated,
};
constructor(props) {
super(props);
this.state = {
xOffset: new Animated.Value(1.0)
};
}
render() {
return (
<View style={styles.container}>
<ScrollView horizontal={true} //水平滑动
showsHorizontalScrollIndicator={false}
style={{width:deviceWidth,height:deviceHeight}}//设置大小
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x绑定给this.state.xOffset
)}
scrollEventThrottle={100}//onScroll回调间隔
>
<Animated.Image source={require('./s1.jpg')}
style={{height:deviceHeight,
width:deviceWidth,
opacity:this.state.xOffset.interpolate({//映照到0.0,1.0之间
inputRange: [0,375],
outputRange: [1.0, 0.0]
}),}}
resizeMode="cover"
/>
<Image source={require('./s2.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
<Image source={require('./s3.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
</ScrollView>
</View>
);
}
}
React Native最经常使用的手势就是PanResponser,
由于本文侧重讲授动画,所以不会特别详细的介绍PanResponser,仅仅介绍用到的几个属性和回调方法
onStartShouldSetPanResponder: (event, gestureState) => {}//是不是相应pan手势
onPanResponderMove: (event, gestureState) => {}//在pan移动的时候进行的回调
onPanResponderRelease: (event, gestureState) => {}//手离开屏幕
onPanResponderTerminate: (event, gestureState) => {}//手势中断
其中,
目标效果- View随着手拖动而移动,手指离开会到原点
核心代码
class Demo extends React.Component {
state:{
trans:AnimatedValueXY,
}
_panResponder:PanResponder;
constructor(props) {
super(props);
this.state = {
trans: new Animated.ValueXY(),
};
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true, //响应手势
onPanResponderMove: Animated.event(
[null, {dx: this.state.trans.x, dy:this.state.trans.y}] // 绑定动画值
),
onPanResponderRelease: ()=>{//手松开,回到原始位置
Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
).start();
},
onPanResponderTerminate:()=>{//手势中断,回到原始位置
Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
).start();
},
});
}
render() {
return (
<View style={styles.container}>
<Animated.View style={{width:100,
height:100,
borderRadius:50,
backgroundColor:'red',
transform:[
{translateY:this.state.trans.y},
{translateX:this.state.trans.x},
],
}}
{...this._panResponder.panHandlers}
>
</Animated.View>
</View>
);
}
}
LayoutAnimation在View由1个位置变化到另外一个位置的时候,在下1个Layout周期自动创建动画。通常在setState前掉用LayoutAnimation.configureNext
代码
class Demo extends React.Component {
state: {
marginBottom:number,
};
constructor(props) {
super(props);
this.state = {//设置初值
marginBottom:0
};
}
_textUp(){
LayoutAnimation.spring();
this.setState({marginBottom:this.state.marginBottom + 100})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress = {()=>this._textUp()}
style={{ width:120,
height:40,
alignItems:'center',
marginBottom:this.state.marginBottom,
justifyContent:'center',
backgroundColor:'#00ffff',
borderRadius:20}}>
<Text>Text UP</Text>
</TouchableOpacity>
</View>
);
}
}
其实代码里只是调用了这1行LayoutAnimation.spring();
,布局修改的时候就显得不那末僵硬了
//配置下1次切换的效果,其中config可配置的包括duration(时间),create(配置新的View),update(配置更新的View)
static configureNext(config, onAnimationDidEnd?)
//configureNext的方便方法
static create(duration, type, creationProp) #
对应3种时间函数
easeInEaseOut: CallExpression #
linear: CallExpression #
spring: CallExpression #
我们先创建1个默许的Navigator转场Demo
回拉的时候,前1个时图的移动距离要小于后1个视图
这时候候的核心代码以下,MainScreen和DetailScreen就是带1个Button的视图
class Demo extends React.Component{
render(){
return (
<Navigator
style = {styles.container}
initialRoute={{id:"main",}}
renderScene={this.renderNav}
configureScene={(route, routeStack) => Navigator.SceneConfigs.PushFromRight}
/>
);
}
renderNav(route,nav){
switch (route.id) {
case 'main':
return <MainScreen navigator={nav} title="Main"/ >;
case 'detail':
return (<DetailScreen navigator={nav} title="Detail"/ >);
}
}
}
Navigator的默许的转场动画的实现都可以在这里找到NavigatorSceneConfigs.js。
So,我们有两种方式来实现自定义的转场动画
篇幅限制,本文只修改默许的转场
比如,我想把默许的PushFromRight动画中,第1个视图的移动距离改成全屏幕。
var ToTheLeftCustom = {
transformTranslate: {
from: {x: 0, y: 0, z: 0},
to: {x: -SCREEN_WIDTH, y: 0, z: 0},//修改这1行
min: 0,
max: 1,
type: 'linear',
extrapolate: true,
round: PixelRatio.get(),
},
opacity: {
value: 1.0,
type: 'constant',
},
};
var baseInterpolators = Navigator.SceneConfigs.PushFromRight.animationInterpolators;
var customInterpolators = Object.assign({}, baseInterpolators, {
out: buildStyleInterpolator(ToTheLeftCustom),
});
var baseConfig = Navigator.SceneConfigs.PushFromRight;
var CustomPushfromRight = Object.assign({}, baseConfig, {
animationInterpolators: customInterpolators,
});
然后,修改Navigator的configScene
configureScene={(route, routeStack) => baseConfig}
这时候候的动画以下
上一篇 BT是怎么下载的