如果你以前从未听说过祝酒词,请看看下面的视频,它展示了我们在本教程中要一起建立的项目的简短演示。
简而言之,祝酒词是一种通知,它在你的显示屏顶部显示一段短暂的时间,之后就会再次消失。
在本教程中,我们将通过使用React Native动画库在React Native中从头开始创建这样一个祝酒词的过程React Native是一个基于JavaScript的框架,它使构建跨平台(iOS和Android)的移动应用程序变得相当容易。请随时查看React Native文档以获得更多信息。
先决条件
我使用Expo框架来开发这个小项目。如果你也想使用Expo,但还没有安装它,你可以按以下方法添加Expo客户端。
$ npm install --global expo-cli
如果你不想使用Expo,下面的代码一般也应该适用于你。这里唯一与Expo有关的是expo**/vector-icons,**它们只有在使用Expo时才会出现。但这没有问题,一个很好的替代方案是React Native Vector Icons!
除此之外,JavaScript和React Native的基本知识将帮助你轻松地学习本教程。如果你想跟得更紧,你可以在我的GitHub上找到整个项目。
开始吧
闲话少说,让我们深入了解一下实际的代码吧!首先,进入你要存放项目的目录。在这个目录下,运行expo init my-project ,以便初始化Expo项目。你可以用你喜欢的名字替换 "my-project"。
然后,用cd my-project 到新创建的目录,运行expo start 来启动开发服务器。Expo让你决定你想用什么样的设备来工作;我在演示和上面的视频中使用的设备是iPhone 12 Pro Max。
下面是对终端命令的简短概述。
# cd into the directory where to store your project
$ cd dir
# initialize the expo project
$ expo init my-project
# navigate inside the newly created project
$ cd my-project
# run the development server
$ expo start
关于项目目录的说明
我通常在最初生成的项目目录中添加一些自定义目录。在这种特殊情况下,我们只需要screens 文件夹,在那里我们将存储我们的Home.js 文件。这就是我们需要添加到初始结构中的全部内容,因为我们只有一个屏幕。吐司信息将在该屏幕中进行编码,所以我们甚至不需要一个component 目录。当然,你也可以把代码从Home.js ,外包给它自己的组件文件。
The React Native Animated library
就像我在本文的介绍中提到的,我们将使用一个叫做React Native Animated的库,它将帮助我们轻松地执行流畅的动画效果。
整个项目的总体思路遵循以下步骤。
- 最初,敬酒信息在视口中是不可见的。
- 一旦其中一个按钮被点击,一个函数将被触发,这将确保敬酒信息被顺利地移到屏幕的顶部。
- 最后,在我们定义的时间窗口后,敬酒信息将再次消失。
这整个过程可以用React Native Animated来实现。下面,你可以看到我们要写的实现这一行为的代码的第一部分。
// Home.js
import React, { useRef, useState } from "react";
import {
StyleSheet,
Text,
View,
Animated,
Button,
TouchableOpacity,
Dimensions,
} from "react-native";
import { AntDesign, Entypo } from "@expo/vector-icons";
const Home = () => {
const windowHeight = Dimensions.get("window").height;
const popAnim = useRef(new Animated.Value(windowHeight * -1)).current;
const popIn = () => {
Animated.timing(popAnim, {
toValue: windowHeight * 0.35 * -1,
duration: 300,
useNativeDriver: true,
}).start(popOut());
};
const popOut = () => {
setTimeout(() => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 300,
useNativeDriver: true,
}).start();
}, 2000);
};
const instantPopOut = () => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 150,
useNativeDriver: true,
}).start();
};
);
};
export default Home;
在第15行,我们使用React Native的Dimensions库获得设备的实际显示高度。这将确保我们的吐司信息的定位将根据实际的显示尺寸来动态呈现。
在第16行,我们定义了我们的敬酒信息的初始位置popAnim 与。
const popAnim = useRef(new Animated.Value(windowHeight * -1)).current;
我们使用useRef Hook,因为React Native Animated告诉我们不要直接修改Animated.value ,而是使用useRef Hook,以便创建一个可变的对象。
你还会注意到,我们正在用当前显示高度的负数初始化Animated.value 。这样做的原因是,最初,我们不想看到敬酒信息。通过(windowHeight * -1) ,敬酒信息将暂时显示在实际视口的上方。
在下面的函数中,我们将定义在某些情况下popAnim 的值和变化速度。在第18行,我们声明了popIn 函数。就像它的名字一样,这个函数处理祝酒词的显示方式。
const popIn = () => {
Animated.timing(popAnim, {
toValue: windowHeight * 0.35 * -1,
duration: 300,
useNativeDriver: true,
}).start(popOut());
};
在上面的第2行,我们调用了timing() 方法,它最终只将我们的popAnim 的值沿着一个定时的缓和曲线做成动画。通过toValue ,你可以告诉Animated在哪个值上停止动画,通过duration ,你决定动画的持续时间。
在我们的例子中,我们希望敬酒信息在300毫秒内显示在屏幕的顶部。我们还想把useNativeDriver 设为true ,以便使用本地代码来执行动画。在我们的小例子中,这个选项不会对我们的动画产生任何影响。
最后,在第6行,我们在我们的timing 方法上调用start 方法,以便实际启动动画。在start 方法中,一旦动画完成,可以调用一个回调函数。在这种情况下,我们要调用popOut 函数!
const popOut = () => {
setTimeout(() => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 300,
useNativeDriver: true,
}).start();
}, 2000);
};
这看起来与popIn 函数非常相似,除了三件事。
- 我们将
toValue属性再次改为windowHeight * -1,这将使敬酒信息再次移到我们的视口之外 - 我们用一个
setTimeOut函数来包装整个事情,这样敬酒信息就不会立即消失,而是在两秒钟内可见。 - 最后,我们没有给这个
start方法提供一个回调函数。
在敬酒信息中,我们将有一个关闭按钮,这也需要一个动画功能。由于我是从头开始构建的,我把它称为instantPopOut 。
const instantPopOut = () => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 150,
useNativeDriver: true,
}).start();
};
这与popOut 函数中的代码几乎相同,只是我们在这里不需要一个setTimeOut 函数,因为一旦点击关闭按钮,我们希望敬酒信息立即消失。
而这一部分实际上是这个项目中最难的部分。剩下的部分相对容易--现在,我们只需要为这两个按钮和敬酒信息编写实际的用户界面代码。
编码用户界面
首先,我们要对按钮进行编码,它将触发敬酒信息的出现。这一部分将包含两个按钮。
- 一个用于显示成功信息
- 一个用于显示失败信息
请看下面的屏幕截图。
<Button
title="Success Message"
onPress={() => {
setStatus("success");
popIn();
}}
style={{ marginTop: 30 }}
></Button>
<Button
title="Fail Message"
onPress={() => {
setStatus("fail");
popIn();
}}
style={{ marginTop: 30 }}
></Button>
如果你看一下上面的代码片段,你会发现这些按钮看起来很相似。只有第4行和第13行的标题和状态是不同的。当你点击第一个标题为 "成功信息 "的按钮时,状态将被设置为成功;否则,将被设置为失败。


我们稍后会回到这个状态,当我们为敬酒信息编码时。在这之前,我们需要在这两种情况下调用popIn() 函数。
编写祝酒词
现在,让我们进入用户界面的有趣部分:实际的敬酒信息
<Animated.View
style={[
styles.toastContainer,
{
transform: [{ translateY: popAnim }],
},
]}
>
<View style={styles.toastRow}>
<AntDesign
name={status === "success" ? "checkcircleo" : "closecircleo"}
size={24}
color={status === "success" ? successColor : failColor}
/>
<View style={styles.toastText}>
<Text style={{ fontWeight: "bold", fontSize: 15 }}>
{status === "success" ? successHeader : failHeader}
</Text>
<Text style={{ fontSize: 12 }}>
{status === "success" ? successMessage : failMessage}
</Text>
</View>
<TouchableOpacity onPress={instantPopOut}>
<Entypo name="cross" size={24} color="black" />
</TouchableOpacity>
</View>
</Animated.View>
在上面的代码片段中,你可以看到整个敬酒信息被包裹在一个Animated.View 。试着把它想象成一个普通的视图元素,只不过你可以在它上面运行动画。
在第2-7行,你可以看到我给这个Animated.View ,分配了一个样式数组。第一个,styles.toastContainer ,是一个普通的React Native样式表参考。
但第二个--{ transform: [{ translateY: popAnim }] } --才是这里有趣的部分。变换接受成吨的变换对象,如rotate ,scale ,或translate (就像在这个例子中)。
更具体地说,我们想沿y轴转换这个Animated.View ,并使用popAnim 作为这个转换的值。记住,这是Animated.Value ,它将根据popIn 和popOut 函数中的规则进行改变。
在第9-29行,我们定义了实际的敬酒信息。这个敬酒信息的第一个组成部分是一个AntDesign图标,它将根据我们的状态success ,有条件地呈现。
下面一行将实现,如果状态被设置为success ,就会呈现一个检查图标。
name={status === "success" ? "checkcircleo" : "closecircleo"}
否则,将呈现一个十字图标。同样的逻辑被应用于图标的颜色**(第13行**)、敬酒信息的标题文本**(第18行**)和这个容器内的实际信息**(第21行**)。我们在这些行中提到的常量,可以在下面的片段中找到。
const successColor = "#6dcf81";
const successHeader = "Success!";
const successMessage = "You pressed the success button";
const failColor = "#bf6060";
const failHeader = "Failed!";
const failMessage = "You pressed the fail button";
这里唯一将以相同方式呈现的部分,是关闭图标。通过点击这个图标,代码onPress={instantPopOut} ,将确保触发instantPop 函数。
就这样了!所有的代码片段都可以总结为以下内容。
// Home.js
import React, { useRef, useState } from "react";
import {
StyleSheet,
Text,
View,
Animated,
Button,
TouchableOpacity,
Dimensions,
} from "react-native";
import { AntDesign, Entypo } from "@expo/vector-icons";
const Home = () => {
const windowHeight = Dimensions.get("window").height;
const [status, setStatus] = useState(null);
const popAnim = useRef(new Animated.Value(windowHeight * -1)).current;
const successColor = "#6dcf81";
const successHeader = "Success!";
const successMessage = "You pressed the success button";
const failColor = "#bf6060";
const failHeader = "Failed!";
const failMessage = "You pressed the fail button";
const popIn = () => {
Animated.timing(popAnim, {
toValue: windowHeight * 0.35 * -1,
duration: 300,
useNativeDriver: true,
}).start(popOut());
};
const popOut = () => {
setTimeout(() => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 300,
useNativeDriver: true,
}).start();
}, 10000);
};
const instantPopOut = () => {
Animated.timing(popAnim, {
toValue: windowHeight * -1,
duration: 150,
useNativeDriver: true,
}).start();
};
return (
<View>
<Animated.View
style={[
styles.toastContainer,
{
transform: [{ translateY: popAnim }],
},
]}
>
<View style={styles.toastRow}>
<AntDesign
name={status === "success" ? "checkcircleo" : "closecircleo"}
size={24}
color={status === "success" ? successColor : failColor}
/>
<View style={styles.toastText}>
<Text style={{ fontWeight: "bold", fontSize: 15 }}>
{status === "success" ? successHeader : failHeader}
</Text>
<Text style={{ fontSize: 12 }}>
{status === "success" ? successMessage : failMessage}
</Text>
</View>
<TouchableOpacity onPress={instantPopOut}>
<Entypo name="cross" size={24} color="black" />
</TouchableOpacity>
</View>
</Animated.View>
<Button
title="Success Message"
onPress={() => {
setStatus("success");
popIn();
}}
style={{ marginTop: 30 }}
></Button>
<Button
title="Fail Message"
onPress={() => {
setStatus("fail");
popIn();
}}
style={{ marginTop: 30 }}
></Button>
</View>
);
};
export default Home;
const styles = StyleSheet.create({
toastContainer: {
height: 60,
width: 350,
backgroundColor: "#f5f5f5",
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
toastRow: {
width: "90%",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-evenly",
},
toastText: {
width: "70%",
padding: 2,
},
});
最后,你需要做的是将Home.js 文件导入到App.js 文件中,就像下面的代码片断一样。
// App.js
import React from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import Home from './screens/Home';
export default function App() {
return (
<SafeAreaView style={styles.container}>
<Home />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
结语
在这篇博文中,我们从头开始创建了一个吐司信息。我们没有使用任何外部库--我们唯一需要的是React Native Animated库。此外,根据我们想看到的是成功还是失败的信息,我们有条件地渲染了敬酒信息。
整个项目的源代码可以在我的GitHub上找到。我希望你觉得这个教程对你有帮助
The postCreating React Native animated toast messages from scratchappeared first onLogRocket Blog.