首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Animations

动画对于创造出色的用户体验非常重要。固定物体在开始移动时必须克服惯性。运动中的物体有动力,很少立即停下来。动画可让您在界面中传达身体上可信的动作。

React Native提供了两个互补的动画系统:Animated用于精确和交互式控制特定值以及LayoutAnimation动画全局布局事务。

Animated API

AnimatedAPI旨在以非常高效的方式简明扼要地表达各种有趣的动画和交互模式。Animated侧重于输入和输出之间的声明性关系,以及两者之间的可配置变换,以及简单start/ stop方法来控制基于时间的动画执行。

Animated出口4种动画组件类型:ViewTextImage,和ScrollView,但你也可以自己用创建Animated.createAnimatedComponent()

例如,安装时淡入的容器视图可能如下所示:

代码语言:javascript
复制
import React from 'react';
import { Animated, Text, View } from 'react-native';

class FadeInView extends React.Component {
  state = {
    fadeAnim: new Animated.Value(0),  // Initial value for opacity: 0
  }

  componentDidMount() {
    Animated.timing(                  // Animate over time
      this.state.fadeAnim,            // The animated value to drive
      {
        toValue: 1,                   // Animate to opacity: 1 (opaque)
        duration: 10000,              // Make it take a while
      }
    ).start();                        // Starts the animation
  }

  render() {
    let { fadeAnim } = this.state;

    return (
      <Animated.View                 // Special animatable View
        style={{
          ...this.props.style,
          opacity: fadeAnim,         // Bind opacity to animated value
        }}
      >
        {this.props.children}
      </Animated.View>
    );
  }
}

// You can then use your `FadeInView` in place of a `View` in your components:
export default class App extends React.Component {
  render() {
    return (
      <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
        <FadeInView style={{width: 250, height: 50, backgroundColor: 'powderblue'}}>
          <Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text>
        </FadeInView>
      </View>
    )
  }
}

让我们来分解这里发生的事情。在FadeInView构造函数中,一个新的Animated.Value调用fadeAnim被初始化为一部分stateView映射到此动画值的不透明属性。在幕后,提取数字值并用于设置不透明度。

当组件加载时,不透明度设置为0.然后,在fadeAnim动画值上启动缓动动画,该动画值将在每帧上更新其所有相关映射(在这种情况下,仅为不透明度),作为动画值最终值为1。

这是以比调用setState和重新渲染更快的优化方式完成的。

由于整个配置是声明性的,我们将能够实现进一步的优化,将配置序列化并在高优先级线程上运行动画。

配置动画

动画是非常可配置的。自定义和预定义的缓动函数,延迟,持续时间,衰减因子,弹簧常数等都可以根据动画的类型进行调整。

Animated提供了几种动画类型,最常用的一种Animated.timing()。它支持使用各种预定义的缓动功能之一随时间推移动画值,或者您可以使用自己的。缓动功能通常用于动画来传递对象的逐渐加速和减速。

默认情况下,timing将使用easeInOut曲线,将渐进加速传递到全速,并通过逐渐减速停止结束。您可以通过传递easing参数来指定不同的缓动函数。自定义duration甚至delay在动画开始之前也支持。

例如,如果我们想要在移动到最终位置之前稍微备份一个物体,并创建一个长度为2秒的动画:

代码语言:javascript
复制
Animated.timing(
  this.state.xPosition,
  {
    toValue: 100,
    easing: Easing.back,
    duration: 2000,
  }                              
).start();

查看AnimatedAPI参考的“配置动画”部分,了解有关内置动画支持的所有配置参数的更多信息。

组成动画

动画可以组合并按顺序或并行播放。连续动画可以在上一个动画结束后立即播放,或者可以在指定的延迟后开始播放。所述AnimatedAPI提供了几种方法,如sequence()delay(),其中的每一个简单地采取动画执行的阵列和自动呼叫start()/ stop()根据需要。

例如,以下动画惯性停止,然后在并行旋转时弹回:

代码语言:javascript
复制
Animated.sequence([            // decay, then spring to start and twirl
  Animated.decay(position, {   // coast to a stop
    velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
    deceleration: 0.997,
  }),
  Animated.parallel([          // after decay, in parallel:
    Animated.spring(position, {
      toValue: {x: 0, y: 0}    // return to start
    }),
    Animated.timing(twirl, {   // and twirl
      toValue: 360,
    }),
  ]),
]).start();                    // start the sequence group

如果一个动画停止或中断,则该组中的所有其他动画也会停止。Animated.parallel有一个stopTogether选项可以设置false为禁用此选项。

您可以在AnimatedAPI参考的“合成动画”部分找到合成方法的完整列表。

结合动画值

您可以通过添加,乘法,除法或模数来组合两个动画值,以创建新的动画值。

在某些情况下,动画值需要反转另一个动画值进行计算。一个例子是反转比例尺(2x - > 0.5x):

代码语言:javascript
复制
const a = Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
  toValue: 2,
}).start();

插值

每个属性都可以先通过插值运行。插值将输入范围映射到输出范围,通常使用线性插值,但也支持缓动功能。默认情况下,它将推断超出给定范围的曲线,但也可以使曲线限制输出值。

将0-1范围转换为0-100范围的简单映射将是:

代码语言:javascript
复制
value.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100],
});

例如,您可能想要考虑Animated.Value从0到1,但将位置从150px设置为0px,并将不透明度从0设置为1.这可以通过style从上面的示例进行修改来轻松完成,如下所示:

代码语言:javascript
复制
  style={{
    opacity: this.state.fadeAnim, // Binds directly
    transform: [{
      translateY: this.state.fadeAnim.interpolate({
        inputRange: [0, 1],
        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0
      }),
    }],
  }}

interpolate()支持多个范围段,这对定义死区和其他方便的技巧非常方便。例如,要获得-300处的否定关系,该关系在-100处变为0,然后在0处回到1,然后在100处回到零,然后在除此之外的所有区域都保持为0的死区,你可以这样做:

代码语言:javascript
复制
value.interpolate({
  inputRange: [-300, -100, 0, 100, 101],
  outputRange: [300,    0, 1,   0,   0],
});

这将映射如下:

代码语言:javascript
复制
Input | Output
------|-------
  -400|    450
  -300|    300
  -200|    150
  -100|      0
   -50|    0.5
     0|      1
    50|    0.5
   100|      0
   101|      0
   200|      0

interpolate()还支持映射到字符串,允许您使用单位设置动画的颜色和值。例如,如果你想动画旋转,你可以这样做:

代码语言:javascript
复制
value.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg']
})

interpolate()还支持任意缓动功能,其中许多功能已在Easing模块中实现。interpolate()也有可配置的行为来推断outputRange。您可以通过设置设置的外推extrapolateextrapolateLeftextrapolateRight选项。默认值是,extend但可以clamp用来防止输出值超出outputRange

跟踪动态值

动画值也可以跟踪其他值。只需将toValue动画设置为另一个动画值而不是普通数字。例如,像在Android使用信使的一个“聊天头”动画可以与实施spring()寄托在另一个动画值,或者与timing()duration0刚性跟踪。它们也可以用插值组成:

代码语言:javascript
复制
Animated.spring(follower, {toValue: leader}).start();
Animated.timing(opacity, {
  toValue: pan.x.interpolate({
    inputRange: [0, 300],
    outputRange: [1, 0],
  }),
}).start();

leaderfollower动画值将采用以下方式实现Animated.ValueXY()ValueXY是处理2D交互的便捷方式,例如平移或拖动。它是一个简单的包装,基本上包含两个Animated.Value实例和一些帮助函数,通过它们进行调用,从而在许多情况下ValueXY进行替换Value。它允许我们在上面的例子中跟踪x和y值。

跟踪手势

手势,如平移或滚动,以及其他事件可以直接映射到使用动画值Animated.event。这是通过结构化地图语法完成的,以便可以从复杂的事件对象中提取值。第一个级别是允许跨多个参数映射的数组,并且该数组包含嵌套对象。

例如,使用水平滚动手势时,您需要执行以下操作以映射event.nativeEvent.contentOffset.xscrollX(an Animated.Value):

代码语言:javascript
复制
 onScroll={Animated.event(
   // scrollX = e.nativeEvent.contentOffset.x
   [{ nativeEvent: {
        contentOffset: {
          x: scrollX
        }
      }
    }]
 )}

使用时PanResponder,可以使用以下代码从gestureState.dx和中提取x和y位置gestureState.dy。我们null在数组的第一个位置使用a ,因为我们只关心传递给PanResponder处理程序的第二个参数,这就是gestureState

代码语言:javascript
复制
onPanResponderMove={Animated.event(
  [null, // ignore the native event
  // extract dx and dy from gestureState
  // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
  {dx: pan.x, dy: pan.y}
])}

回应当前的动画值

您可能会注意到在动画制作中没有明显的方法来读取当前值。这是因为优化可能只会在本机运行时知道该值。如果您需要针对当前值运行JavaScript,则有两种方法:

  • spring.stopAnimation(callback)将停止动画并callback以最终值调用。这在做手势转换时很有用。
  • spring.addListener(callback)callback在动画运行时异步调用,提供最近的值。这对于触发状态更改非常有用,例如当用户将它拖拽得更近时,将跳动捕捉到新选项,因为与连续手势相比,这些较大的状态更改不太敏感,比如需要在60 FPS。

Animated被设计为完全可序列化的,因此可以以高性能方式运行动画,而不依赖于正常的JavaScript事件循环。这确实会影响API,所以请记住,与完全同步的系统相比,执行某些操作似乎有点麻烦。退房Animated.Value.addListener的方式来解决这些局限性,但要节制利用,因为它可能在未来的业绩产生影响。

使用本机驱动程序

AnimatedAPI被设计为可序列化。通过使用本地驱动程序,我们会在开始动画之前将所有关于动画的内容发送到本地,从而允许本地代码在UI线程上执行动画,而无需通过每一帧的桥接。一旦动画开始,JS线程就可以被阻塞而不会影响动画。

对普通动画使用本地驱动程序非常简单。启动时只需添加useNativeDriver: true到动画配置中即可。

代码语言:javascript
复制
Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true, // <-- Add this
}).start();

动画值只与一个驱动程序兼容,因此如果在对某个值启动动画时使用本机驱动程序,请确保该值上的每个动画也使用本地驱动程序。

本地驱动程序也适用于Animated.event。由于React Native的异步特性,这对于在没有本地驱动程序的情况下滚动位置后面的动画特别有用,动画将始终在手势后面运行一帧。

代码语言:javascript
复制
<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
  scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
  onScroll={Animated.event(
    [{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
    { useNativeDriver: true } // <-- Add this
  )}
>
  {content}
</Animated.ScrollView>

您可以通过运行RNTester应用程序来查看本机驱动程序,然后加载本地动画示例。您还可以查看源代码以了解这些示例的制作方式。

注意事项

Animated本地驱动程序目前不支持您所能做的所有事情。主要的限制是,你只能动画非布局属性:之类的东西transform,并opacity会工作,但Flexbox的和position属性不会。使用时Animated.event,它只能用于直接事件而不是冒泡事件。这意味着它不起作用,PanResponder但可以处理类似的事情ScrollView#onScroll

记住

虽然使用变换样式(如rotateY,,rotateX等)确保了变换样式perspective已到位。目前,如果没有它,某些动画可能无法在Android上呈现。下面的例子。

代码语言:javascript
复制
<Animated.View
  style={{
    transform: [
      { scale: this.state.scale },
      { rotateY: this.state.rotateY },
      { perspective: 1000 } // without this line this Animation will not render on Android while working fine on iOS
    ]
  }}
/>

其他例子

RNTester应用程序有各种Animated使用示例:

LayoutAnimation API

LayoutAnimation允许您全局配置createupdate动画,将在下一个渲染/布局循环中用于所有视图。这对于执行flexbox布局更新很有用,而不必费心去测量或计算特定属性以便直接对它们进行动画处理,而且在布局更改可能会影响祖先时特别有用,例如“看更多”扩展也会增加父级的大小并向下推动下面的行,否则将需要组件之间的明确协调以使它们全部同步地动画化。

请注意,尽管LayoutAnimation功能非常强大并且非常有用,但Animated与其他动画库相比,它提供的控制要少得多,所以如果无法LayoutAnimation按照自己的想法操作,则可能需要使用其他方法。

请注意,为了让它在Android上运行,您需要通过以下方式设置以下标志UIManager

代码语言:javascript
复制
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
代码语言:javascript
复制
import React from 'react';
import {
  NativeModules,
  LayoutAnimation,
  Text,
  TouchableOpacity,
  StyleSheet,
  View,
} from 'react-native';

const { UIManager } = NativeModules;

UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true);

export default class App extends React.Component {
  state = {
    w: 100,
    h: 100,
  };

  _onPress = () => {
    // Animate the update
    LayoutAnimation.spring();
    this.setState({w: this.state.w + 15, h: this.state.h + 15})
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={[styles.box, {width: this.state.w, height: this.state.h}]} />
        <TouchableOpacity onPress={this._onPress}>
          <View style={styles.button}>
            <Text style={styles.buttonText}>Press me!</Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'red',
  },
  button: {
    backgroundColor: 'black',
    paddingHorizontal: 20,
    paddingVertical: 15,
    marginTop: 15,
  },
  buttonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
});

本示例使用预设值,您可以根据需要自定义动画,请参阅LayoutAnimation.js以获取更多信息。

补充笔记

requestAnimationFrame

requestAnimationFrame是您可能熟悉的浏览器的填充。它接受一个函数作为唯一的参数并在下一次重绘之前调用该函数。它是所有基于JavaScript的动画API的基础动画基础构件。一般来说,你不需要自己调用它 - 动画API将为你管理帧更新。

setNativeProps

如直接操作部分所述,setNativeProps允许我们直接修改本机支持的组件(实际由本机视图支持的组件)的属性,而无需setState重新呈现组件层次结构。

我们可以在Rebound示例中使用它来更新比例 - 如果我们正在更新的组件深度嵌套并且未使用优化组件,这可能会有所帮助shouldComponentUpdate

如果您发现丢帧的动画(每秒低于60帧),请查看使用setNativePropsshouldComponentUpdate优化它们。或者,您可以在UI线程上运行动画,而不是使用useNativeDriver选项运行JavaScript线程。您可能还想使用InteractionManager推迟任何计算密集型工作,直到动画完成后。您可以使用应用内开发人员菜单“FPS监视器”工具来监视帧速率。

扫码关注腾讯云开发者

领取腾讯云代金券