首发于公众号 前端混合开发,欢迎关注。
现代移动应用程序在入门过程中经常涉及一个步骤,你需要输入发送到你的电子邮件或手机号码的验证码 PIN。有时,你需要使用类似于分割 OTP 输入字段的东西来输入 PIN。另一种输入验证码 PIN 的方式是使用拨号盘。
"OTP" 指的是 "一次性密码" (One-Time Password)。这是一种安全机制,用于通过短信或电子邮件向用户发送一次性使用的密码或验证码,以验证用户的身份。
在这篇文章中,我们将展示如何为 React Native 应用创建一个定制的数字键盘。构建一个定制的 React Native 数字键盘可以作为分割输入或传统 TextInput 元素的优秀替代品,以个性化你的移动应用设计。
你可以查看我们的React Native项目的完整源代码,并随着我们一步步设置数字键盘进行跟踪。让我们开始吧。
在React Native应用中数字键盘的使用场景
在React Native应用中,有许多专业的数字键盘使用场景。
一个常见的例子是一次性密码(OTP)输入验证。例如,假设你在新用户入门过程中,向他们的手机发送了一个OTP。发送OTP后,用户将被引导到一个屏幕上,使用数字键盘输入并验证它。
另一个使用场景是为你的应用添加一层安全防护,这对于包含敏感信息的应用来说非常重要。当你的用户重新登录你的应用时,你可以为他们展示一个数字键盘,他们可以在此输入一个PIN码,你的应用在让他们登录前需要验证这个PIN码。
在我们的教程中,我们将创建这第二种用例的一个简单示例。我们将看到如何在 React Native 中从头开始设置一个数字键盘,以便用户可以创建一个 PIN 并使用该 PIN 登录应用。
设置开发环境
运行以下命令以快速启动一个Expo应用:
npx create-expo-app my-app
上述命令将创建我们所需的基础React Native项目文件。完成后,启动iOS或Android模拟器上的开发服务器:
//for iOS
npm run ios
//for Android
npm run android
这是你项目文件夹中 App.js 文件内代码的输出。
创建、渲染和设计React Native数字键盘
在这个部分,我们将开始创建三个屏幕: Login
, CustomDialpad
和 Home
。
Login
屏幕将是用户初次加载应用时看到的第一个屏幕。它将有一个按钮,可以将用户引导到 CustomDialpad
屏幕,在那里他们可以输入他们的PIN码。一旦输入正确的PIN码,应用将会将用户引导到 Home
屏幕。
我们开始构建我们的React Native应用程序,包含这三个屏幕。首先,安装我们需要设置和配置React Native基本导航的以下包:
npx install @react-navigation/native @react-navigation/native-stack react-native-safe-area-context react-native-screens
另外,创建一个名为 screens
的文件夹,并在其中放入三个文件: Login.jsx
, CustomDialPad.jsx
和 HomeScreen.jsx
。
接下来,在你的 App.js
文件中,按照下面所示实现基本的导航:
import { StyleSheet } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import HomeScreen from "./screens/HomeScreen";
import Login from "./screens/Login";
import CustomDialPad from "./screens/CustomDialPad";
const Stack = createNativeStackNavigator();
export default function App() {
return (
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
在这里,我们将整个应用程序包裹在 NavigationContainer
内,并使用 Stack.Navigator
组件来管理屏幕堆栈。当用户导航到一个屏幕时,它会被推到堆栈的顶部。然后,当用户导航到另一个页面时,它会从堆栈顶部弹出屏幕。
在这种情况下,堆栈顶部的初始屏幕将是 Login
屏幕。当用户按下按钮导航到 CustomDialpad
屏幕时, CustomDialpad
屏幕会被推到 Login
屏幕的上方,依此类推:
现在屏幕导航已经全部设置好了,我们可以开始设置数字键盘的逻辑和用户界面。
设置 CustomDialpad.jsx 文件
在根目录中创建一个 component
文件夹,并在其中添加一个 DialpadKeypad.jsx
文件。稍后我们将在此文件中构建我们的数字键盘界面和功能。
然后,在 CustomDialpad.jsx
文件中导入 DialpadKeypad.jsx
组件文件:
//CustomDialPad.js
import {
SafeAreaView,
StyleSheet,
Text,
View,
Dimensions,
} from "react-native";
import React, { useState } from "react";
import { Ionicons } from "@expo/vector-icons";
import DialpadKeypad from "../components/DialpadKeypad";
const { width, height } = Dimensions.get("window");
我们还使用了 Dimensions.get
方法来提取用户设备的屏幕 width
和 height
。这将帮助我们确保我们的用户界面能够响应不同的屏幕尺寸进行适应。
接下来,为了开始构建我们的React Native数字键盘,我们首先需要创建一些变量:
const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"];
const dialPadSize = width * 0.2;
const dialPadTextSize = dialPadSize * 0.4;
const pinLength = 4;
我们简要回顾一下每个变量的目的:
dialPadContent
— 我们将在数字键盘 UI 上显示的内容。在这种情况下,我们想要显示一个由十二个值组成的数组,这些值被排列在一个三列四行的网格中。pinLength
— 用户应输入的PIN码长度。我们希望用户输入一个四位数的PIN码,但这可以根据你的项目需求进行调整。dialPadSize
— 数字键盘的大小,由手机屏幕的 width 乘以 0.2 得出,占屏幕 width 的20%dialPadTextSize
— 显示在数字键盘内的文本大小,由将 dialPadSize 值乘以0.4
得到 dialPadSize 的40%来确定
在 CustomDialpad.jsx
文件的其余部分,我们定义了 CustomDialPad
组件,并使用 useNavigation
Hook使我们能够控制屏幕导航。我们还设置了组件结构和样式,并导出自定义组件,使其可以在应用的其他部分中使用:
const CustomDialPad = () => {
const navigation = useNavigation();
return (
navigation.goBack()}
style={{ position: "absolute", top: -5, left: 10 }}
>
Create PIN
Enter your secure four-digit code
);
};
export default CustomDialPad;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FAF0E6",
},
textContainer: {
justifyContent: "center",
alignItems: "center",
marginTop: 40,
position: "relative",
},
pinText: {
fontSize: 30,
fontWeight: "medium",
color: "#5E454B",
},
pinSubText: {
fontSize: 18,
fontWeight: "medium",
color: "#5E454B",
marginVertical: 30,
},
}
此外,我们使用 TouchableOpacity
组件实现了一个返回按钮,使用户能够通过 navigation.goBack()
方法返回到 Login 页面。
设置 DialpadKeypad.jsx 文件
现在让我们在 DialpadKeypad.js
文件中工作。首先,我们将导入所有必要的模块和组件:
import {
StyleSheet,
Text,
View,
FlatList,
TouchableOpacity,
} from "react-native";
import React from "react";
import { Feather } from "@expo/vector-icons";
接下来,让我们拿到我们在 CustomDialPad.js
文件中传递给组件的属性,并用它们来构建键盘的用户界面。然后,我们将使用 Flatlist
来渲染我们之前定义的 dialPadContent
数组。
const DialpadKeypad = ({
dialPadContent,
pinLength,
navigation,
dialPadSize,
dialPadTextSize,
}) => {
return (
index.toString()}
renderItem={({ item }) => {
return (
{item === "X" ? (
) : (
{item}
)}
);
}}
/>
);
};
我们将 numColumns
属性设置为 3
,以便在三列中渲染我们的 dialPadContent
数组。数组中的空白 ""
值使我们可以使渲染的三列四行数字键盘在视觉上更加平衡。
在数字键盘上,我们使空白按钮不能被按压,并移除了它的背景色。我们还为数组中对应 X
值的按钮渲染了一个删除图标。对于数字键盘上的其余按钮,我们渲染了数组中的数字。
我们还将 View
组件包裹在 TouchableOpacity
组件内,以渲染 dialpadContent
。
我们在这个文件中的最后一步是定义我们组件的样式:
export default DialpadKeypad;
const styles = StyleSheet.create({
dialPadContainer: {
justifyContent: "center",
alignItems: "center",
margin: 10,
borderRadius: 50,
borderColor: "transparent",
},
dialPadText: {
color: "#3F1D38",
},
});
我们看看我们目前拥有的React Native数字键盘:
集成并限制点击功能
我们设置在键盘上按下按钮时的功能。我们使用一个初始数据类型为数组的状态来跟踪键盘上每个按钮按下的值。然后,这将作为一个属性传递给 DialpadKeypad
组件。
在 DialpadKeypad
文件中,我们将采用 code
和 setCode
属性,并使用它们来实现所需的功能。当点击 Keypad
内容时,我们将首先调用 onPress
属性进行检查:
- 如果按下的按钮的值为
X
。如果是这样,它应该删除数组中的最后一个项目——换句话说,删除最后选择的PIN值。 - 如果按下的按钮的值是除了
X
之外的任何值。如果是,它应该使用setCode
属性将选中的项目添加到代码数组中。 - 如果代码数组的长度等于
pinLength - 1
。如果是这样,应该将用户导航到Home
屏幕。
我们使用 pinLength - 1
对 code
属性的长度进行对比,是因为所需的 pinLength
被指定为 4 。
如果 code
状态数组中有四个项目,长度将为 3
,因为数组中的索引值从 0
开始。因此,一旦将四位数的PIN输入到 code
数组中,我们就使用 pinLength -1
来导航到 Home
屏幕。
为了实现所有这些,我们需要像这样更新 CustomDialPad.js
文件中的代码:
const CustomDialPad = () => {
const navigation = useNavigation();
const [code, setCode] = useState([]);
// rest of the code
同样地,像这样更新 DialpadKeypad.js
文件:
const DialpadKeypad = ({
dialPadContent,
pinLength,
code,
setCode,
navigation,
dialPadSize,
dialPadTextSize,
}) => {
// rest of the code
{
if (item === "X") {
setCode((prev) => prev.slice(0, -1));
} else {
if (code.length === pinLength - 1) {
navigation.navigate("Home");
}
setCode((prev) => [...prev, item]);
}
}}
>
为输入的PIN添加一个 MultiView
在这一部分,我们将添加一个 MultiView
。在这个实例中,这是一个视图,允许我们查看所选输入 — 换句话说,就是输入的 PIN 码。
首先,在组件文件夹中创建一个 DialpadPin.js
文件,并在 CustomDialPad
组件中渲染它。然后,我们将 pinLength
, pinSize
, code
和 dialPadContent
属性传递给 DialpadPin.js
文件。
在 DialpadPin.js
文件中,我们将根据我们之前设定的 4 的PIN长度渲染一个 View 。我们希望在 CustomDialpad
屏幕上将其作为四个均匀分布的圆形排列在输入PIN的提示和数字键盘之间显示。
在渲染的视图内部,我们还将渲染 PIN 值,这将让我们知道是否已选择了一个值。如果从键盘上选择了一个值,我们将在 MultiView
中显示它,这样用户就知道他们当前在输入中选择了多少位数字。
要实现所有这些,请按照以下方式更新 CustomDialPad.js
文件:
const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"];
const dialPadSize = width * 0.2;
const dialPadTextSize = dialPadSize * 0.4;
const pinLength = 4;
const pinContainerSize = width / 2;
const pinSize = pinContainerSize / pinLength;
const CustomDialPad = () => {
const fontsLoaded = useCustomFonts();
const navigation = useNavigation();
const [code, setCode] = useState([])
// rest of the code
然后,也更新 DialpadPin.js 文件:
import { StyleSheet, Text, View } from "react-native";
import React from "react";
const DialpadPin = ({ pinLength, pinSize, code, dialPadContent }) => {
return (
{Array(pinLength)
.fill()
.map((_, index) => {
const item = dialPadContent[index];
const isSelected =
typeof item === "number" && code[index] !== undefined;
return (
{isSelected && (
)}
);
})}
);
};
export default DialpadPin;
const styles = StyleSheet.create({
dialPadPinContainer: {
flexDirection: "row",
marginBottom: 30,
alignItems: "flex-end",
},
pinContentContainer: {
flex: 1,
backgroundColor: "#fff",
borderWidth: 1,
justifyContent: "center",
alignItems: "center",
},
pinContent: {
backgroundColor: "#5E454B",
},
});
现在,让我们看看我们有什么:
我们可以进一步去动画化从数字键盘中选中的点状引脚。在 DialpadPin.jsx
文件中,导入 Animated
库,这是React Native提供的开箱即用的。然后,用 Animated.View
包裹显示点状选择的 View 。
{isSelected && (
)}
现在我们将创建一个 useEffect
钩子,每当代码的值发生变化时都会触发。每当用户在键盘上输入一个数字,都会使用 Animation.timing
方法触发动画。 animatedValue 将从其当前值动画过渡到 code.length
值,过程持续 300
毫秒。
const DialpadPin = ({ pinLength, pinSize, code, dialPadContent }) => {
const animatedValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(animatedValue, {
toValue: code.length,
duration: 300,
useNativeDriver: true,
}).start();
}, [code]);
接下来,我们将使用 animatedStyle
样式对象在键盘上选择数字时应用缩放转换:
const animatedStyle = {
transform: [
{
scale: animatedValue.interpolate({
inputRange: [index, index + 1],
outputRange: [1, 1.3],
extrapolate: "clamp",
}),
},
],
};
我们在这里使用了 interpolate
方法将输入值映射到输出值,确保动画的流畅。 inputRange
和 outputRange
属性定义了插值的值。
最后, extrapolate
属性定义了输出值的行为。它的 clamp
值表示输出值在定义的范围内被限制。将 animatedStyle
对象添加到 Animated.View
的样式输入中:
{isSelected && (
)}
我们添加动画后的最终结果应如下所示:
如你所见,彩色的点首先以稍微小一些的形式出现在 MultiView 气泡中,然后扩大以更完全地填充气泡。这使我们的数字键盘功能在不过分分散注意力的情况下,以一种微妙的方式变得更具视觉吸引力。
附加说明和建议
为了在真实的React Native应用中改进这个数字键盘的实现,我们需要设置一个后端服务来与我们的前端实现进行通信。让我们回顾一下这对我们每个用例会涉及到什么。
我们讨论的第一个用例是在新用户注册过程中,使用数字键盘验证发送到用户手机或电子邮件的一次性密码。因此,当有新用户注册你的应用时,你需要:
- 验证他们用来注册的电子邮件
- 从你的后端服务发送一次性密码
- 指导他们到一个包含数字键盘的屏幕,他们可以在那里输入你发送到他们邮箱的一次性密码
现在,用户需要使用数字键盘输入他们收到的OTP。理想情况下,当他们输入完整的OTP后,你应该能够向后端的 verify
端点发送请求,以验证你发送给该用户的OTP是否与他们在前端输入的匹配
- 如果匹配,将他们导航至 Home 屏幕
- 如果不匹配,显示一个定制的错误信息,告诉他们输入的PIN码错误,他们应该输入发送到他们邮箱的正确PIN码
在我们当前的项目中,我们没有验证PIN,因为我们没有设置后端服务。然而,如果你在一个真实的项目中设置这个, verify
端点应该在 DialpadKeypad.j
s 文件中被调用,我们在那里检查 code.length
与 pinLength
:
{
if (item === "X") {
setCode((prev) => prev.slice(0, -1));
} else {
if (code.length === pinLength - 1) {
// 一旦用户输入了所需的 PIN 长度,在这里调用端点
navigation.navigate("Home");
}
setCode((prev) => [...prev, item]);
}
}}
>
我们讨论的第二个用例是使用数字键盘进行登录安全。就像第一个用例一样,你可以在你的应用程序中自定义数字键盘,显示在你的登录页面上。
用户在注册时可以输入一个PIN码。然后,当用户重新输入他们的PIN码以重新登录应用时,你可以让你的后端端点验证在注册期间创建的密码是否与正在输入的密码匹配。
如果你的后端端点验证了匹配,你可以允许用户登录。如果没有,你可以显示一个定制的警告消息 - 例如, Pin does not match
。
这个用例确保用户在没有必要的安全检查的情况下,不会仅仅进入应用程序。
比较创建自定义数字键盘的方法
React Native支持几种不同的创建数字键盘的方法。例如,我们可以使用 TextInput
组件,并将键盘类型作为 numeric
来设置我们的数字键盘。然而,这种方法存在一些已知的问题:
- 点击组件外部时无法消除:这个问题意味着即使你在
TextInput
外部点击,数字键盘仍然保持打开状态。解决这个问题的可能方法是使用TouchableWithoutFeedback
API组件,在你点击它外部时消除 TextInput 键盘。 - 按返回键未能消除:这个问题意味着当你按下返回键时,数字键盘不会自动消失
也有一些现有的开源库提供数字键盘功能,包括 React Native Numpad
和 React Native Numeric Pad
。然而,这些库在功能和可定制性方面有些限制。
在许多情况下,你的React Native应用可能有独特的设计和特定的需求,关于数字键盘功能应该如何构建和实施。构建自定义功能意味着你不会受到库的能力的限制。
此外,在你的React Native应用程序中安装过多的包会使其变得臃肿。自行构建功能并减少安装的包可以帮助减小应用程序的大小。
最后,库可能不会持续活跃地维护,甚至可能完全被遗弃,这可能会导致你的应用崩溃。如果你选择使用第三方库,始终尝试使用稳定且维护良好的选项。
你选择的方法取决于你的项目需求。例如,使用库可以帮助你节省大量的开发时间。然而,如果你需要特定的功能或定制,那么投入时间来构建你自己的可能会更好。
总结
在这篇文章中,我们学习了如何在React Native中创建自定义数字键盘。我们还将我们的方法与其他选项进行了比较,比如内置的 TextInput 组件和第三方开源库,以更好地理解何时以及为什么要从头开始构建这个功能。
自定义数字键盘是一款出色的移动应用功能,适用于像使用一次性密码验证用户或让他们使用PIN登录等情况。你可以在这个仓库中找到我们演示项目的完整源代码。
交流
首发于公众号 大迁世界,欢迎关注。 每周一篇实用的前端文章 ️ 分享值得关注的开发工具 ❓ 有疑问?我来回答
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。