83

React Native中的动画过渡

 5 years ago
source link: http://www.10tiao.com/html/216/201806/2652561335/2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近我在为下一个动画设计试着找新灵感,并在一个网站上看到了一些。我很好奇能否用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 (
     <Animated.View style={animatedStyle}>{this.props.children}</Animated.View>
   );
 }
}


现在让我们看看怎么用react-native-motion实现它。我知道经常有些动画很特别,不过React Native提供了一个强大的动画API。不管怎么说,拥有一个基础动画库总是好的。


import { TranslateYAndOpacity } from 'react-native-motion';

class ToolbarTitle extends PureComponent {
 render() {
   return (
     <TranslateYAndOpacity duration={250}>
       <View>
         // ...
       </View>
     </TranslateYAndOpacity>

   );
 }
}


共享元素


这个动画最大的问题是移动从列表中选择的物品。这个物品被列表页(ListPage)与详情页(DetailPage)共享,怎么在元素事实上并没有绝对地放置的时候,把一个物品从列表移动到详情页的顶部?使用React Native动作就会简单做到。

// List items page with source of SharedElement
import { SharedElement } from 'react-native-motion';
class ListPage extends Component {
 render() {
   return (
     <SharedElement id="source">
       <View>{listItemNode}</View>
     </SharedElement>

   );
 }
}


我们定义了列表页中SharedElement的源要素。我们对详情页中的目的元素做类似的事,这样就能知道我们要把共享元素移动到的位置。


// Detail page with a destination shared element
import { SharedElement } from 'react-native-motion';

class DetailPage extends Component {
 render() {
   return (
     <SharedElement sourceId="source">
       <View>{listItemNode}</View>
     </SharedElement>

   );
 }
}


重点在哪里?


我们怎么把一个相对放置的元素从一个页面移动到另一个呢?很可惜这并不能做到。实际上SharedElement是这样运行的:


  • 为源元素获取一个位置

  • 获取目的元素的位置(显然,没有这步动画就无法开始)

  • 为共享元素创建一个克隆(重点!)

  • 在屏幕上渲染一个新的图层

  • 渲染这个克隆元素,使它覆盖源元素(在它的位置上)

  • 开始移动到目标位置

  • 一旦到达目标位置,移除克隆元素



你可以想象同时存在3个同样React Node的元素。因为在这个动画中列表页被详情页覆盖。这就是为什么我们可以看到全部3元素。但是我们制造了一个好像移动了最初物品的假象。


SharedElement时间轴


你可以看到A点和B点,它们之间的区域代表移动过程中的时间。你还可以看到SharedElement激活了一些有用的事件。在本例中,我们使用WillStart和DidFinish事件,根据你的实际需要为源和目标元素设置不透明度,当开始向目标移动时为0,一旦动画完成把目标元素重新设为1。


相关推荐:



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK