开启掘金成长之旅!这是我参与「掘金日新计划 · 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>;
};
效果:
如果想在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>;
};
效果:
除了上面代码中所使用的显示声明create、update、delete来构建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相关介绍)。