最近我在为下一个动画设计试着找新灵感,并在一个网站上看到了一些。我很好奇能否用React Native实现这些过渡。
我们可以看到上图包含了几个动画,工具栏(展示/隐藏),底边条(展示/隐藏),移动选定物品,隐藏所有其他物品,展示物品细节等等。
动画的时间轴
这个过渡的难点是让这些动画同步。我们不能真的取消挂载列表页并展示详情页面,因为我们需要等待所有的动画放完。
同样我也喜欢简洁,易于维护的代码。如果你曾经尝试过为你的项目添加动画,代码经常会很乱,充满各种辅助参数与头疼的计算。这就是为什么我会推荐react-native-motion。
react-native-motion的思想
你有留意到工具栏标题的动画吗?你只是移动标题一点,并把不透明度在0/1间变化,并不是多复杂的操作。但是为了这些,你要写这样的代码,这还没考虑到你实际为这个组件编写UI。
class TranslateYAndOpacity extends PureComponent { constructor(props) { // ... this.state = { opacityValue: new Animated.Value(opacityMin), translateYValue: new Animated.Value(translateYMin), }; // ... } componentDidMount() { // ... this.show(this.props); // ... } componentWillReceiveProps(nextProps) { if (!this.props.isHidden && nextProps.isHidden) { this.hide(nextProps); } if (this.props.isHidden && !nextProps.isHidden) { this.show(nextProps); } } show(props) { // ... Animated.parallel([ Animated.timing(opacityValue, { /* ... */ }), Animated.timing(translateYValue, { /* ... */ }), ]).start(); } hide(props) { // ... Animated.parallel([ Animated.timing(opacityValue, { /* ... */ }), Animated.timing(translateYValue, { /* ... */ }), ]).start(); } render() { const { opacityValue, translateYValue } = this.state; const animatedStyle = { opacity: opacityValue, transform: [{ translateY: translateYValue }], }; return ({this.props.children} ); } }
现在让我们看看怎么用react-native-motion实现它。我知道经常有些动画很特别,不过React Native提供了一个强大的动画API。不管怎么说,拥有一个基础动画库总是好的。
import { TranslateYAndOpacity } from 'react-native-motion'; class ToolbarTitle extends PureComponent { render() { return (); } } // ...
共享元素
这个动画最大的问题是移动从列表中选择的物品。这个物品被列表页(ListPage)与详情页(DetailPage)共享,怎么在元素事实上并没有绝对地放置的时候,把一个物品从列表移动到详情页的顶部?使用React Native动作就会简单做到。
// List items page with source of SharedElement import { SharedElement } from 'react-native-motion'; class ListPage extends Component { render() { return (); } } {listItemNode}
我们定义了列表页中SharedElement的源要素。我们对详情页中的目的元素做类似的事,这样就能知道我们要把共享元素移动到的位置。
// Detail page with a destination shared element import { SharedElement } from 'react-native-motion'; class DetailPage extends Component { render() { return (); } } {listItemNode}
重点在哪里?
我们怎么把一个相对放置的元素从一个页面移动到另一个呢?很可惜这并不能做到。实际上SharedElement是这样运行的:
为源元素获取一个位置
获取目的元素的位置(显然,没有这步动画就无法开始)
为共享元素创建一个克隆(重点!)
在屏幕上渲染一个新的图层
渲染这个克隆元素,使它覆盖源元素(在它的位置上)
开始移动到目标位置
一旦到达目标位置,移除克隆元素
你可以想象同时存在3个同样React Node的元素。因为在这个动画中列表页被详情页覆盖。这就是为什么我们可以看到全部3元素。但是我们制造了一个好像移动了最初物品的假象。
SharedElement时间轴
你可以看到A点和B点,它们之间的区域代表移动过程中的时间。你还可以看到SharedElement激活了一些有用的事件。在本例中,我们使用WillStart和DidFinish事件,根据你的实际需要为源和目标元素设置不透明度,当开始向目标移动时为0,一旦动画完成把目标元素重新设为1。