最近我在为下一个动画设计试着找新灵感,并在一个网站上看到了一些。我很好奇能否用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。