转载

React Native中的动画过渡

最近我在为下一个动画设计试着找新灵感,并在一个网站上看到了一些。我很好奇能否用React Native实现这些过渡。

 React Native中的动画过渡

我们可以看到上图包含了几个动画,工具栏(展示/隐藏),底边条(展示/隐藏),移动选定物品,隐藏所有其他物品,展示物品细节等等。

React Native中的动画过渡

动画的时间轴

这个过渡的难点是让这些动画同步。我们不能真的取消挂载列表页并展示详情页面,因为我们需要等待所有的动画放完。

同样我也喜欢简洁,易于维护的代码。如果你曾经尝试过为你的项目添加动画,代码经常会很乱,充满各种辅助参数与头疼的计算。这就是为什么我会推荐react-native-motion

 React Native中的动画过渡

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是这样运行的:

  • 为源元素获取一个位置

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

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

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

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

  • 开始移动到目标位置

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

 React Native中的动画过渡

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

React Native中的动画过渡

SharedElement时间轴

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

正文到此结束
Loading...