React Native LayoutAnimation

652 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

前言

React Native(后面简称RN)根据对动画控制粒度的不同,为开发者提供了两套完善的动画API:

  • LayoutAnimation:控制粒度较大,用来实现布局排版动画,实现较为简单。
  • Animated API:用于创建精细的交互控制的动画,可以进行多个动画的组合。

借助这些API,我们可以轻松的开发出流畅的动画,本篇文章主要介绍LayoutAnimation使用。

简单使用

LayoutAnimation能够在布局发生变化的时候自动的创建和更新动画,并且在下一次渲染或者布局周期运行。为了方便的使用LayoutAnimation,RN为我们提供了三个预设好了动画参数的高级API,分别对应三种预设定好的动画:

  • LayoutAnimation.easeinEaseOut:缓入缓出动画
  • LayoutAnimation.linear:线性动画
  • LayoutAnimation.spring:弹性动画

只需要在类似setState等会引起布局变化的代码前加上相关配置即可。

举个栗子:

import React, { useState } from "react";
import { Button, Image, LayoutAnimation, View } from "react-native";

export const ShowLayoutAnimation = () => {
  const [size, setSize] = useState(100);
  const start = () => {
    LayoutAnimation.spring();
    setSize((prevSize) => {
      return prevSize + 20;
    });
  };
  const reset = () => {
    LayoutAnimation.easeInEaseOut();
    setSize(100);
  };
  return <View style={{ flex: 1, width: "100%", alignItems: "center", justifyContent: "center" }}>
    <Image style={{ width: size, height: size }}
           source={require("../assets/react-icon.png")}
           resizeMode={"contain"} />
    <View style={{ position: "absolute", bottom: 30, flexDirection: "row" }}>
      <MyButton title={"start"} onPress={start} />
      <MyButton title={"reset"} onPress={reset} />
    </View>
  </View>;
};

const MyButton = (props: { title: string, onPress: () => void }) => {
  return <View style={{ borderRadius: 5, backgroundColor: "#1c75ff", marginHorizontal: 10, paddingHorizontal: 10 }}>
    <Button title={props.title} color={"white"} onPress={props.onPress} />
  </View>;
};

效果:

Untitled.gif

如果想在Android上也使用LayoutAnimation的话,需要添加以下代码开启该功能

if (
  Platform.OS === "android" &&
  UIManager.setLayoutAnimationEnabledExperimental
) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

修改预设值

上面三种预设动画,对应的动画参数在源码中如下:

// LayoutAnimation.js 116
const Presets = {
  easeInEaseOut: (create(
    300,
    'easeInEaseOut',
    'opacity',
  ): LayoutAnimationConfig),
  linear: (create(500, 'linear', 'opacity'): LayoutAnimationConfig),
  spring: {
    duration: 700,
    create: {
      type: 'linear',
      property: 'opacity',
    },
    update: {
      type: 'spring',
      springDamping: 0.4,
    },
    delete: {
      type: 'linear',
      property: 'opacity',
    },
  },
};

我们可以通过LayoutAnimation.Presets修改这些预设值。

LayoutAnimation.Presets.spring.duration = 1000;
LayoutAnimation.spring();

这样的话我们开启的spring动画就是1秒的时长,其他字段的作用会在后面介绍。需要注意的是,当下次执行LayoutAnimation.spring进行动画,时长还会是我们上次设置的1秒。

自定义动画

除了上述修改预设值的方式修改动画,我们还可以通过configureNextAPI更具体的配置下一次布局更新时执行的动画。

configureNext长这样:

configureNext: (
  config: LayoutAnimationConfig,
  onAnimationDidEnd?: () => void,
  onAnimationDidFail?: () => void,
) => void;

其中onAnimationDidEnd是动画结束的监听。onAnimationDidFail当动画产生错误的时候会调用,按照官方文档的说明,在旧的渲染引擎中,这个方法永远不会调用,在新的Fabric渲染引擎中,只有在配置解析异常(config配置错误)的情况下会被调用,开发时大概率用不上这个参数。而剩下的config就是我们配置动画的参数。

config的类型长这样

export interface LayoutAnimationConfig {
	// 配置动画时长(ms)
  duration: number;
	// 配置组件被加载时执行的动画
  create?: LayoutAnimationAnim | undefined;
	// 配置组件更新时执行的动画 
  update?: LayoutAnimationAnim | undefined;
	// 配置组件被卸载时执行的动画
  delete?: LayoutAnimationAnim | undefined;
}

export interface LayoutAnimationAnim {
	// 单个动画时长(ms),会覆盖config中的动画时长
  duration?: number | undefined;
	// 延迟指定时长(ms)
  delay?: number | undefined;
	// 配置弹性动画的阻尼系数, 仅作用于spring类型动画
  springDamping?: number | undefined;
	// 初始速度
  initialVelocity?: number | undefined;
	// 动画类型: 'spring' | 'linear' | 'easeInEaseOut' | 'easeIn' | 'easeOut' | 'keyboard';
  type?: LayoutAnimationType | undefined;
	// 动画作用的属性: 'opacity' | 'scaleX' | 'scaleY' | 'scaleXY';
  property?: LayoutAnimationProperty | undefined;
}

再举个栗子~:

import React, { useState } from "react";
import { Button, Image, LayoutAnimation, View } from "react-native";

export const ShowLayoutAnimation = () => {
  const [showImg, setShowImage] = useState(false);
  const [imageSize, setImageSize] = useState(100);
  const createAnimation = () => {
    LayoutAnimation.configureNext({
      duration: 500,
      create: { type: "easeIn", property: "opacity" },
    });
    setShowImage(true);
  };
  const updateAnimation = () => {
    LayoutAnimation.configureNext({
      duration: 500,
      update: { duration: 1000, type: "linear", property: "scaleXY" },
    });
    setImageSize((prevSize) => prevSize + 100);
  };
  const deleteAnimation = () => {
    LayoutAnimation.configureNext({
      duration: 500,
      delete: { type: "easeOut", property: "opacity" },
    }, () => setImageSize(100));
    setShowImage(false);
  };

  return <View style={{ flex: 1, width: "100%", alignItems: "center", justifyContent: "center" }}>

    {showImg && <Image style={{ width: imageSize, height: imageSize }}
                       source={require("../assets/react-icon.png")}
                       resizeMode={"contain"} />}

    <View style={{ position: "absolute", bottom: 30, flexDirection: "row" }}>
      <MyButton title={"create"} onPress={createAnimation} />
      <MyButton title={"update"} onPress={updateAnimation} />
      <MyButton title={"delete"} onPress={deleteAnimation} />
    </View>
  </View>;
};

const MyButton = (props: { title: string, onPress: () => void }) => {
  return <View style={{ borderRadius: 5, backgroundColor: "#1c75ff", marginHorizontal: 10, paddingHorizontal: 10 }}>
    <Button title={props.title} color={"white"} onPress={props.onPress} />
  </View>;
};

效果:

Untitled 1.gif

除了上面代码中所使用的显示声明createupdatedelete来构建LayoutAnimationConfig,还可以通过createAPI来快速构建一个简单的config,具体实现如下:

function create(duration: number, type: LayoutAnimationType, property: LayoutAnimationProperty,): LayoutAnimationConfig {
  return {
    duration,
    create: {type, property},
    update: {type},
    delete: {type, property},
  };
}

总结

到这里LayoutAnimation的功能我们已经基本体验完了,下面总结一下优缺点:

  • 优点
    • 实现非常简单,最少可以一行代码实现动画
    • 可以修改动画类型、动画时长及部分动画参数,基本满足单个动画的需求
  • 缺点
    • 无法对单个动画进行更精细的控制(例如:更多的属性支持,无法进行插值等)
    • 无法对多个动画进行组合

总的来说,在实际使用中,当我们只需要简单的进行布局变化的过渡时,使用LayoutAnimation是个不错的选择,如果要实现复杂动画的话,则需要使用Animated相关API(挖个坑,后续再来一篇Animated相关介绍)。