创建“隔离专家”——React Native 中的趣味学习实验
自2020年初以来,新冠疫情改变了我们的生活方式——对于我们中的一些人来说,这一年简直就像电视遥控器上的快进键一样,恨不得快进到第二天。话虽如此,自我隔离似乎是目前最好的选择,因此,在隔离期间,我们萌生了一个有趣又简单的React Native应用创意。
这款应用的核心在于记录你的隔离时长。用户只需输入开始隔离的日期,应用就会显示一条有趣的信息,告诉你这场隔离“游戏”你已经走了多远。
也就是说,除了趣味性之外,本教程还将向您展示如何使用最新版本37.x.x.的Expo SDK构建一个演示应用程序。您将学习到:
- 如何使用Expo 字体钩子;
- 如何使用日期时间选择器模态框选择日期;
- 使用Moment.js转换用户提供的数据输入,并计算当前日期与原日期之间的差异。
以下是本教程中我们将要构建的内容的预览:
您可以在此GitHub 仓库中找到本教程的完整代码。
创建一个新的 Expo 应用
首先,在本地开发环境中,选择你最喜欢的副业项目位置,创建一个新的 Expo 应用。在新终端窗口中运行以下命令,使用expo-cli.
npx expo-cli init DaVinciOfIsolation
当被要求选择模板时,请blank从以下模板中选择Managed workflow:
之后,按回车键,让 expo-cli 安装启动此项目所需的依赖项。
项目初始化生成完成后,从终端窗口进入项目目录,启动 Expo 打包服务。
expo start
这将在您选择的已安装 Expo 客户端的模拟器或设备上启动 Expo 应用。有关如何安装 Expo 客户端的更多信息,请访问官方文档。
应用在 Expo 客户端运行后,您将看到以下默认屏幕:
让我们通过执行以下命令来安装构建此项目所需的 npm 依赖项:
expo install expo-font @use-expo/font @react-native-community/datetimepicker
expo install它使用与 Expo SDK 兼容的特定版本添加依赖项。
npm install此外,请使用以下方法安装以下 npm 包yarn:
yarn add react-native-modal-datetime-picker moment
至此,我们已经安装了所需的所有 npm 依赖项。接下来,让我们开始构建应用程序。
如何在 Expo 应用中使用自定义字体
安装新字体
在这个应用中,我们将使用一种可以从 Google Fonts 免费下载的特定自定义字体。点击此处Press Start 2P即可下载。
要使用此字体或任何其他自定义字体,请fonts在assets/文件夹内创建一个名为 `fonts` 的新目录。然后将您刚刚下载的字体文件放入其中。此字体目录路径./assets/fonts是 Expo 开发人员建议在应用程序中放置自定义字体时使用的约定。
将文件放入新创建的目录中后,文件结构将如下所示。
在下载字体用于 Expo React Native 应用时,请确保下载 .ts.otf或 .ts.ttf格式的字体。这两种格式适用于所有 Expo 平台,例如 Web、iOS 和 Android。
使用useFonts钩子
要在 React 或 React Native 应用中使用任何Hook,都必须使用函数式组件。要设置新字体,首先需要导入以下语句。
import React from 'react';
import { View, Text } from 'react-native';
import { useFonts } from '@use-expo/font';
import { AppLoading } from 'expo';
该useFonts钩子函数接收一个 JavaScript 对象作为参数,并返回一个包含单个元素的列表,该元素的值指示字体是否已加载。这样就省去了编写大量样板代码来进行此检查的麻烦。
导入语句后,创建一个名为 的新对象customFont。它将有一个键——字体本身的名称——以及该键的值——目录中字体文件的路径assets/fonts/。
const customFont = {
'Press-Start2p': require('./assets/fonts/PressStart2P-Regular.ttf')
};
接下来,在函数组件内部,定义钩子isLoaded中的变量,并将对象作为其参数useFonts传递。customFont
此外,当字体处于加载状态或尚未加载时,最佳实践是使用AppLoadingExpo 的组件,并且屏幕上不渲染任何内容。字体加载完成后,屏幕将显示该功能组件的内容。
以下是该组件的完整代码App。目前,我们正在使用刚刚安装的新字体显示应用程序标题。
export default function App() {
const [isLoaded] = useFonts(customFont);
if (!isLoaded) {
return <AppLoading />;
}
return (
<View
style={{
flex: 1,
alignItems: 'center',
backgroundColor: '#ffbd12'
}}
>
<Text
style={{
fontFamily: 'Press-Start2p',
fontSize: 24,
marginTop: 80,
paddingHorizontal: 20
}}
>
{`Are You a Quarantine Pro?`}
</Text>
</View>
);
}
根据上面的代码片段,请务必在组件fontFamily上描述该属性Text。这是确保特定文本组件能够使用该字体的唯一方法。
返回 Expo 客户端,您将看到以下结果。
搞定!您已完成在 React Native 应用中加载和使用字体的第一步。感谢Cedric van Putten,他简化了字体加载和映射的过程。如需了解更多信息,请查看 Cedric 的 Expo 应用钩子集合(点击此处)。
创建一个按钮以使用日期时间选择器模态框
由于我们已经安装了显示日期选择器模态框所需的 npm 依赖项(使用原生日期选择器模块),让我们在当前App.js文件中添加一个按钮来显示此模态框。
首先按照以下说明修改导入语句,并添加新的语句。
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
Dimensions,
TouchableWithoutFeedback
} from 'react-native';
import {
Fontisto,
MaterialCommunityIcons,
FontAwesome
} from '@expo/vector-icons';
import DateTimePickerModal from 'react-native-modal-datetime-picker';
为了设置按钮的宽度和高度,我们将使用Dimensions核心 API react-native。按钮的宽度和高度将根据当前窗口的宽度计算得出。
在函数组件之前,定义一个变量W来表示窗口的宽度App。
const W = Dimensions.get('window').width;
接下来,在应用标题文本之后,定义另一个View按钮容器组件。由于 React Native 的可触摸组件只允许包含子组件,因此我们将按钮的内容包装TouchableWithoutFeedback在一个单独的View组件中。不过,我们将有两个子组件:按钮图标和文本。请App根据以下代码片段修改组件的返回语句。
return (
<View style={styles.container}>
<Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
<TouchableWithoutFeedback>
<View style={styles.pickerContainer}>
<Fontisto style={styles.icon} name="calendar" size={48} />
<Text style={styles.pickerText}>{`Tap here to\nselect a date`}</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
为上面的代码片段添加以下样式。让我们使用该StyleSheet对象来管理当前组件文件中的样式。
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#ffbd12'
},
title: {
fontFamily: 'Press-Start2p',
fontSize: 24,
marginTop: 80,
paddingHorizontal: 20,
lineHeight: 30
},
pickerContainer: {
marginTop: 20,
backgroundColor: '#00c6ae',
width: W / 1.2,
height: W / 4,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
borderBottomWidth: 5,
borderBottomColor: '#000',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row'
},
pickerText: {
fontFamily: 'Press-Start2p',
fontSize: 14,
paddingHorizontal: 10,
lineHeight: 20
},
icon: {
color: '#000'
}
});
刷新 Expo 客户端即可获得以下结果。
现在,让我们将日期选择器模态框绑定到这个按钮。我们已经导入了react-native-modal-datetime-picker此步骤所需的 npm 包。我们之所以使用这个库而不是默认库,@react-community/react-native-datetimepicker是因为这个特殊的库提供了一个跨平台接口,用于在模态框中显示原生日期选择器和时间选择器。
我们的应用还会根据用户选择的日期来计算用户已经隔离的天数。为此,我们将使用useStateReact 的 hook 定义一些状态变量,原因如下:
pickedDate存储用户选择的日期;isDatePickerVisible显示或隐藏日期选择器模态框。
我们需要定义三个辅助函数以及这些状态变量。前两个函数用于控制日期选择器模态框的可见性。第三个函数用于控制日期选择器模态框中的确认按钮——即用户选择日期后要执行的操作。这里我们要执行的操作是隐藏日期选择器模态框,并将日期值存储在状态变量中pickedDate。
export default function App() {
// ... rest of the component remains same
const [pickedDate, setPickedDate] = useState(null);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
function showDatePicker() {
setDatePickerVisibility(true);
}
function hideDatePicker() {
setDatePickerVisibility(false);
}
function handleConfirm(date) {
console.log('A date has been picked: ', date);
hideDatePicker();
setPickedDate(date);
}
return (
<View style={styles.container}>
<Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
<TouchableWithoutFeedback onPress={showDatePicker}>
<View style={styles.pickerContainer}>
<Fontisto style={styles.icon} name="calendar" size={48} />
<Text style={styles.pickerText}>{`Tap here to\nselect a date`}</Text>
</View>
</TouchableWithoutFeedback>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date",.
onConfirm={handleConfirm}
onCancel={hideDatePicker}
headerTextIOS="When did you start isolating?"
/>
</View>
}
读取数据
每次用户点击按钮显示选择器模态框时,都会触发此showDatePicker方法。组件仅在该方法触发时才会渲染到设备屏幕上。
当用户点击模态框外的任何位置或点击Cancel按钮时,模态框会再次隐藏,并且不会发生任何事情。
但是,当用户选择日期并点击后Confirm,就可以执行进一步的操作。现在,让我们在控制台语句中显示用户选择的日期。
输出结果显示在终端窗口中运行的 Expo 服务器上。
这意味着用户输入现在存储在状态变量中pickedDate。
此外,您还可以应用其他可用的属性@react-community/react-native-datetimepicker。在我们实现的日期选择器模态框中,使用 `title` 属性进行了一些自定义headerTextIOS。此属性允许更改 iOS 设备上选择器模态框的标题。
评估“隔离评分”
我们当前应用程序中缺失的第二个拼图是缺少一个按钮来计算用户输入的日期与当前日期之间的天数差(我们将以此作为我们的“隔离分数”)。
在设计方面,我们将沿用上一节的策略。显示一个按钮,用户可以点击该按钮查看自己的分数。
moment首先,在其他导入语句之后,在文件中导入该库App.js。它将负责计算用户输入和当前日期之间的对应关系。
// rest of the import statements
import moment from 'moment';
该库还将帮助我们格式化日期选择器模态框中的输入,并仅显示用户输入的日期(而不是时间),格式为YYYY-MM-DD。
修改返回语句,添加一个新View容器,其中包含一条文本消息和一个用于计算天数差的按钮。
此外,在修改return函数组件的语句之前,添加一个名为 `getDifference` 的辅助方法,daysRemaining()用于计算差值。我们将把这个差值存储在一个名为 `state` 的状态变量中days。下一节将使用此状态变量在屏幕上显示正确的结果。
差值将在pickedDate(用户输入的值)和todaysDate(当前日期)之间计算。
export default function App() {
const [days, setDays] = useState('');
function daysRemaining() {
// user's input
let eventdate = moment(pickedDate);
// getting current date
let todaysdate = moment();
let remainingDays = todaysdate.diff(eventdate, 'days');
setDays(remainingDays);
return remainingDays;
}
return (
<View style={styles.container}>
<Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
<TouchableWithoutFeedback onPress={showDatePicker}>
<View style={styles.pickerContainer}>
<Fontisto style={styles.icon} name="calendar" size={48} />
<Text style={styles.pickerText}>{`Tap here to\nselect a date`}</Text>
</View>
</TouchableWithoutFeedback>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date"
onConfirm={handleConfirm}
onCancel={hideDatePicker}
headerTextIOS="When did you start isolating?"
/>
{/* ADD BELOW */}
<View style={styles.showDateContainer}>
<Text style={styles.showDateText}>
You started isolating on{' '}
{pickedDate && (
<Text style={styles.showDateText}>
{moment(pickedDate).format('YYYY-MM-DD')}.
</Text>
)}
</Text>
<TouchableWithoutFeedback onPress={daysRemaining}>
<View style={styles.evaluateButtonContainer}>
<Text style={styles.evaluateButtonText}>Check your level</Text>
</View>
</TouchableWithoutFeedback>
</View>
</View>
}
使用函数将选定的日期以所需格式显示moment().format()。pickedDate只有在用户通过日期选择器模态框选择日期后,才会显示该日期。
以下是上述代码片段对应的样式。
const styles = StyleSheet.create({
// rest of the styles remain same
showDateContainer: {
marginTop: 20,
backgroundColor: '#F95A2C',
width: W / 1.2,
height: W / 2,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
alignItems: 'center'
},
showDateText: {
fontFamily: 'Press-Start2p',
fontSize: 14,
padding: 10,
marginTop: 20,
lineHeight: 20
},
evaluateButtonContainer: {
marginTop: 20,
backgroundColor: '#1947E5',
width: W / 1.4,
height: W / 6,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
borderBottomWidth: 5,
borderBottomColor: '#000',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row'
},
evaluateButtonText: {
color: '#fff',
fontFamily: 'Press-Start2p',
fontSize: 14,
paddingHorizontal: 10,
lineHeight: 20
},
}
这是您在 Expo 客户端中将获得的初始结果。
从日期选择器模态框中选择日期。选择日期后,将显示如下。
渲染“隔离级别”
当前应用程序的最后一部分是当用户按下写着“……”的按钮时显示结果Check your level。
修改组件的返回语句App。当有结果可用时,我们将显示用户的隔离级别;当没有结果可用时,此 UI 框将显示默认消息。紧接着上一节代码片段,添加另一个View容器组件。
评估完成后,renderAchievements()程序将仅返回图标和基于得分(当前日期与用户输入日期之差)的文本消息。由于我们使用一个名为 `distance` 的状态变量days来存储此差值,因此可以轻松地根据条件渲染消息。
export default function App() {
// rest of the code remains the same
function renderAchievements() {
if (days > 1 && days < 5) {
return (
<>
<MaterialCommunityIcons
name="guy-fawkes-mask"
color="#000"
size={54}
/>
<Text style={styles.resultText}>
Quarantine Noob. Don't forget to wear a mask. Keep self-isolating.
</Text>
</>
);
} else if (days >= 5 && days <= 7) {
return (
<>
<MaterialCommunityIcons name="glass-wine" color="#000" size={54} />
<Text style={styles.resultText}>Quarantine Connoisseur. Welcome to the (literal) dark side!</Text>
</>
);
} else if (days >= 8 && days <= 15) {
return (
<>
<MaterialCommunityIcons
name="seat-legroom-reduced"
color="#000"
size={54}
/>
<Text style={styles.resultText}>Quarantine Proficient. AKA “What is pants?”</Text>
</>
);
} else if (days >= 16 && days <= 22) {
return (
<>
<MaterialCommunityIcons
name="star-circle-outline"
color="#000"
size={54}
/>
<Text style={styles.resultText}>Quarantine Veteran. #StayHome became your life motto.</Text>
</>
);
} else if (days >= 23) {
return (
<>
<FontAwesome name="paint-brush" color="#000" size={54} />
<Text style={styles.resultText}>THE ULTIMATE QUARANTINE PRO! You are part of the solution - thank you!</Text>
</>
);
} else
return (
<Text style={styles.resultText}>Your level will be shown here.</Text>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>{`Are You a Quarantine Pro?`}</Text>
<TouchableWithoutFeedback onPress={showDatePicker}>
<View style={styles.pickerContainer}>
<Fontisto style={styles.icon} name="calendar" size={48} />
<Text style={styles.pickerText}>{`Tap here to\nselect a date`}</Text>
</View>
</TouchableWithoutFeedback>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date"
onConfirm={handleConfirm}
onCancel={hideDatePicker}
headerTextIOS="When did you start isolating?"
/>
<View style={styles.showDateContainer}>
<Text style={styles.showDateText}>
You started isolating on{' '}
{pickedDate && (
<Text style={styles.showDateText}>
{moment(pickedDate).format('YYYY-MM-DD')}.
</Text>
)}
</Text>
<TouchableWithoutFeedback onPress={daysRemaining}>
<View style={styles.evaluateButtonContainer}>
<Text style={styles.evaluateButtonText}>Check your level</Text>
</View>
</TouchableWithoutFeedback>
</View>
{/* ADD BELOW */}
<View style={styles.resultContainer}>{renderAchievements()}</View>
</View>
}
以下是一些样式renderAchievements()。
const styles = StyleSheet.create({
// rest of the styles remain same
resultContainer: {
marginTop: 20,
backgroundColor: '#FF89BB',
width: W / 1.2,
height: W / 2,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
justifyContent: 'center',
alignItems: 'center'
},
resultText: {
color: '#fff',
fontFamily: 'Press-Start2p',
fontSize: 16,
padding: 15,
lineHeight: 20
}
});
现在,返回 Expo 客户端,您将看到我们的最终版应用程序!尝试运行该应用程序并选择不同的日期,以查看如下所示的不同结果。
结论
希望您在开发这款应用的过程中既享受乐趣又有所收获。本教程的主要目标现已完成,为了便于理解,总结如下。
- 如何使用Expo 字体钩子;
- 如何使用日期时间选择器模态框选择日期;
- 使用Moment.js转换用户提供的日期输入,并计算当前日期与“隔离分数”之间的差异。
请访问@react-native-community/datetimepicker了解更多关于如何自定义日期选择器模态框的信息,或者尝试使用时间选择器。Moment.js 库提供了丰富的函数,可帮助您在 JavaScript 应用中管理日期和时间(此处还有一篇教程)。
该应用程序可在 Expo现场获取,您只需使用设备上的Expo 客户端应用程序( iOS | Android )扫描二维码即可。
那么,你得了多少分呢?欢迎在推特上@Jscrambler并附上截图——我们将送出一件 Jscrambler T 恤给一位幸运的居家隔离开发者!
文章来源:https://dev.to/jscrambler/creating-quarantine-pro-a-fun-learning-experiment-in-react-native-45
