让我们动起来: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
});
});
};
  {...} 

这里发生的是,在成功地旋转了旋转状态之后,我们将rotationAnimationAnimated.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.ViewAnimated.ImageAnimated.ScrollViewAnimated.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.sequenceAnimated.parallel可以用来创建看起来令人难以置信的复杂动画。 开始实验!

在本系列的下一个也是final(?)部分中,我将使用用户的触摸输入遍历屏幕上的一个元素来进行动画处理。

谢谢阅读!