让我们动起来:React Native动画简介-第2部分
如果您想完全从头开始,这里是第1部分的链接,在第1部分中,我们使用LayoutAnimation动画了单个元素在屏幕上的重新放置。
让我们直接跳回去吧。
在本文中,我们将不再使用LayoutAnimation
,它是一个很好的工具,可用于在要更新的列表中重新定位元素,隐藏/显示菜单等,但是如果您希望动画中的粒度更详细些怎么办? 好的,这就是React Native的Animated
API派上用场的地方。
让我们从上一篇文章中获取Jake,但让我们决定我们要让他旋转到位,并且还要基于某种状态来取消此旋转。 当然,随心所欲,但这是我的文章,所以我在这里负责。
我们将从第一部分结尾处的代码停止处开始,将Jake锁定在屏幕中央,添加某种状态以决定他是否在旋转,并摆脱一些按钮和其他一些按钮清理东西的线。
// App.js
从“反应”中导入React,{组件};
导入{
按钮
StyleSheet,
图片,
视图
}来自“ react-native”;
从“ ./jake.png”导入杰克;
导出默认类App扩展组件{
构造函数(道具){
超级(道具);
this.state = {
旋转:错误
};
}
render(){
返回(
<按钮
style = {styles.button}
标题= {
this.state.spinning? “关闭旋转”:“打开旋转”
}
onPress = {()=> {}}
/>
);
}
}
const styles = StyleSheet.create({
容器: {
弹性:1,
justifyContent:“中心”,
alignItems:“居中”,
backgroundColor:“#F5FCFF”,
paddingTop:64,
paddingBottom:32
},
buttonContainer:{
flexDirection:“行”,
justifyContent:“空间均匀”,
位置:“绝对”,
底部:16
宽度:“ 100%”
},
按钮:{
宽度:100
}
});
现在,让我们来切换spinning
状态并将其附加到按钮的方法。
// App.js
{...}
toggleSpinning =()=> {
this.setState({
旋转:!this.state.spinning
});
};
render(){
返回(
<按钮
onPress = {()=> this.toggleSpinning()}
buttonStyle = {styles.button}
title = {this.state.spinning? “关闭旋转”:“打开旋转”}
/>
);
}
{...}
刷新应用程序,然后检查按钮标题是否来回切换。 当然,没有其他事情了。 要使Jake旋转,我们需要向他提供一些东西来更新他的transform: [{rotate: ... }]
值。 React Native的Animated
API为我们提供了一种简洁的方式来跟踪我们可以操纵的数字(或一对数字),然后将其应用于所选的任何元素,它将处理随之而来的所有更新。
我们需要创建一个称为Animated.Value
,尽管文档显示了将该值存储在状态中,但我倾向于不这样做,因为我们将直接对该值进行突变(而不是通过setState
)。
让我们Animated from "react-native"
导入Animated from "react-native"
并创建此值,我们将直接在构造函数中调用this._rotationAnimation
,然后向当前setState
调用添加回调以更新此新Animated.Value
// App.js
{...}
构造函数(道具){
超级(道具);
this._rotationAnimation =新的Animated.Value(0);
this.state = {
旋转:错误
};
}
}
{...}
toggleSpinning =()=> {
this.setState({旋转:!this.state.spinning},()=> {
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000
});
});
};
{...}
这里发生的是,在成功地旋转了旋转状态之后,我们将rotationAnimation
的Animated.Value
设置为0
,并在2000
毫秒的时间内将其值增加为1
。
如果您刷新应用程序并切换状态,那么什么都不会发生。 我们需要以某种方式将此值的增加从0转换为1,以用于旋转Jake。 幸运的是, Animated
附带了一个可以执行类似操作的工具。
Animated.Value().interpolate
是一个很好的lil函数,可以将两组数字相互映射。 例如,如果为它提供了[0,1]
的outputRange
和[0, 100]
outputRange
[0,1]
的outputRange
并将其绑定到_rotationAnimation
,则当_rotationAnimation === 0
插值函数将返回0
,但是当_rotationAnimation === 1
,它将返回100
。 当它是0.5
,它将返回50
,依此类推。 请记住,我们将_rotationAnimation
在2秒的时间内从0
增加到1
,因此它将以相同的速率增加返回的结果。 您可以内插惊人数量的输出范围(在文档中列出)。 例如,要使该项目正常工作,我们将使用输出范围['0deg', '360deg']
。 如果我们将输出应用于rotate
CSS规则,则图标应该旋转!
我们将创建一个单独的方法来返回此插值,然后在样式中使用它。
// App.js
{...}
getRotationAnimation =()=> {
const rotation = this._rotationAnimation.interpolate({
inputRange:[0,1],
outputRange:[“ 0deg”,“ 360deg”]
});
返回{旋转};
};
{...}
<图像
来源= {杰克}
样式= {{变换:[this.getRotationAnimation()]}}
/>
{...}
刷新应用程序时,应该看到如下错误:console.error: “Unhandled JS Exception: Invariant Violation: Transform with key of “rotate” must be a string: {“rotate”:”0deg”}
这是因为我们没有告诉React Native Jake是我们想要动画的元素。 与LayoutAnimation
不同,我们需要明确声明将对哪些元素进行动画处理。 有两种方法可以做到这一点。 Animated
随附了一些准备好进行动画处理的元素, Animated.View
, Animated.Image
, Animated.ScrollView
和Animated.Text
,可以直接将它们替换为非Animated的对应元素。 另外,还有一个函数Animated.createAnimatedComponent
,它接受一个非Animated组件并返回一个Animated组件。
现在,我们要做的就是将Jake切换为Animated.Image.
<动画图像
来源= {杰克}
样式= {{变换:[this.getRotationAnimation()]}}
/>
现在我们的图标动画切换了吗? 不,因为我犯了几乎总是犯同样的错误,所以我想在这里指出,因为当您有点草率时,它可能会无休止地令人沮丧。 Animated.timing(…)
返回一个对象,但是直到在该对象上运行.start()
动画才开始。
像这样更新我们的toggleSpinning
:
toggleSpinning =()=> {
this.setState({旋转:!this.state.spinning},()=> {
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000
})。开始();
});
};
现在我们正在做生意,但是还没有循环。 实际上,即使在第一次旋转后切换旋转状态也不会执行任何操作,这是因为一旦我们的Animated.Value
增大为1
,我们就再也不会将其设置为0
,而是一直尝试将其从1
更改为1
,这无济于事。 幸运的是,在这种情况下, Animated
提供了另一个简单的解决方案。
我们只将我们Animation
包装在Animated.loop
然后开始:
toggleSpinning =()=> {
this.setState({旋转:!this.state.spinning},()=> {
Animated.loop(
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000
})
)。开始();
});
};
我们的Jake现在正在循环旋转,但感觉有点奇怪。 这是因为默认动画(由“ easeInEaseOut.
功能”定义)设置为easeInEaseOut.
这意味着动画会沿着正弦波加速。
Robert Gummesson的一篇很棒的文章详细展示了这些缓动功能的外观。
为了使我们的循环更平滑,我们需要一个线性缓动函数,以便它始终保持相同的速度。 为了实现这一点,我们Easing from "react-native"
导入“ Easing from "react-native"
,这为我们提供了各种缓动功能的便捷捷径。 在这种情况下,我们想使用Easing.linear
。 我们通过以下键easing:
将其应用于我们的配置块easing:
toggleSpinning =()=> {
this.setState({旋转:!this.state.spinning},()=> {
Animated.loop(
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000,
缓动:Easing.linear
})
)。开始();
});
};
这为平滑,无限旋转的杰克提供了诀窍,但切换旋转状态并不会停止旋转,而是重新启动
他,因为我们的回调函数才开始我们的循环。 我们可以解决这个问题,没问题。
为了清晰起见,让我们提取循环动画:
startLoopAnimation =()=> {
Animated.loop(
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000,
缓动:Easing.linear
})
)。开始();
};
让我们继续创建一个停止方法:
stopLoopAnimation =()=> {
this._rotationAnimation.stopAnimation();
};
并相应地更新我们的切换方法:
toggleSpinning =()=> {
this.setState({旋转:!this.state.spinning},()=> {
此状态旋转
? this.startLoopAnimation()
:this.stopLoopAnimation();
});
};
太好了,现在他停下了脚步,但是有一个令人讨厌的新事物,那就是每次我们重新打开他时,他都会跳回到起始位置。 老实说,我不清楚为什么动画的起始值保持为0,但是幸运的是我们的stopAnimation
为我们提供了一个回调,并在动画停止时将当前的Animated.Value
传递给我们,以便我们可以使用它来在开始备份时抵消我们的动画。
我们创建一个变量来保存该数字,称为_rotationOffset
并将其设置为0
。
构造函数(道具){
超级(道具);
this._rotationAnimation =新的Animated.Value(0);
this._rotationOffset = 0;
this.state = {
旋转:错误
};
}
然后修改我们的stop和start方法以利用它:
startLoopAnimation =()=> {
this._rotationAnimation.setOffset(this._rotationOffset);
Animated.loop(
Animated.timing(this._rotationAnimation,{
值:1
持续时间:2000,
缓动:Easing.linear
})
)。开始();
};
stopLoopAnimation =()=> {
this._rotationAnimation.stopAnimation(currentValue => {
this._rotationOffset = currentValue;
});
};
做到了! 现在,我们可以关闭并打开旋转的Jake,他可以按照我们的要求去做。
您可以在此处查看最终代码。
Animated
的插值与Animated.timing
各种配置以及本文未介绍的其他方法(如Animated.sequence
和Animated.parallel
可以用来创建看起来令人难以置信的复杂动画。 开始实验!
在本系列的下一个也是final(?)部分中,我将使用用户的触摸输入遍历屏幕上的一个元素来进行动画处理。
谢谢阅读!