如何设计React Native OTP输入屏幕?

问题描述 投票:0回答:10

我是 React Native 设计的新手。让我知道如何实现如下所示的屏幕

有必要使用 4 个 TextInput 还是可以使用 1 个?

react-native react-router textinput
10个回答
5
投票

您可以仅使用一个隐藏的 TextInput 元素,并附加一个

onChangeText
函数并填充在文本视图中输入的值(您可以使用设计需要的四种不同的文本视图)。 如果用户单击文本视图,请确保将 TextInput 聚焦在单击上


4
投票

在这里,我创建了一个带有 六个文本输入的屏幕,用于 otp 验证,具有 重新发送 OTP 功能,计数器计时器为 90 秒。在 android 和 ios 上进行了全面测试。

我使用 react-native-confirmation-code-field 进行带下划线的文本输入。 下面是完整的代码。

import React, { useState, useEffect } from 'react';
import { SafeAreaView, Text, View ,TouchableOpacity} from 'react-native';
import { CodeField, Cursor, useBlurOnFulfill, useClearByFocusCell } from 
'react-native-confirmation-code-field';
import { Button } from '../../../components';
import { styles } from './style';

interface VerifyCodeProps {
}
const CELL_COUNT = 6;
const RESEND_OTP_TIME_LIMIT = 90;

export const VerifyCode: React.FC<VerifyCodeProps> = () => {
let resendOtpTimerInterval: any;

const [resendButtonDisabledTime, setResendButtonDisabledTime] = useState(
    RESEND_OTP_TIME_LIMIT,
);

//to start resent otp option
const startResendOtpTimer = () => {
    if (resendOtpTimerInterval) {
        clearInterval(resendOtpTimerInterval);
    }
    resendOtpTimerInterval = setInterval(() => {
        if (resendButtonDisabledTime <= 0) {
            clearInterval(resendOtpTimerInterval);
        } else {
            setResendButtonDisabledTime(resendButtonDisabledTime - 1);
        }
    }, 1000);
};

//on click of resend button
const onResendOtpButtonPress = () => {
    //clear input field
    setValue('')
    setResendButtonDisabledTime(RESEND_OTP_TIME_LIMIT);
    startResendOtpTimer();

    // resend OTP Api call
    // todo
    console.log('todo: Resend OTP');
};

//declarations for input field
const [value, setValue] = useState('');
const ref = useBlurOnFulfill({ value, cellCount: CELL_COUNT });
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
    value,
    setValue,
});

//start timer on screen on launch
useEffect(() => {
    startResendOtpTimer();
    return () => {
        if (resendOtpTimerInterval) {
            clearInterval(resendOtpTimerInterval);
        }
    };
}, [resendButtonDisabledTime]);

return (
    <SafeAreaView style={styles.root}>
        <Text style={styles.title}>Verify the Authorisation Code</Text>
        <Text style={styles.subTitle}>Sent to 7687653902</Text>
        <CodeField
            ref={ref}
            {...props}
            value={value}
            onChangeText={setValue}
            cellCount={CELL_COUNT}
            rootStyle={styles.codeFieldRoot}
            keyboardType="number-pad"
            textContentType="oneTimeCode"
            renderCell={({ index, symbol, isFocused }) => (
                <View
                    onLayout={getCellOnLayoutHandler(index)}
                    key={index}
                    style={[styles.cellRoot, isFocused && styles.focusCell]}>
                    <Text style={styles.cellText}>
                        {symbol || (isFocused ? <Cursor /> : null)}
                    </Text>
                </View>
            )}
        />
        {/* View for resend otp  */}
        {resendButtonDisabledTime > 0 ? (
            <Text style={styles.resendCodeText}>Resend Authorisation Code in {resendButtonDisabledTime} sec</Text>
        ) : (
                <TouchableOpacity
                    onPress={onResendOtpButtonPress}>
                    <View style={styles.resendCodeContainer}>
                        <Text style={styles.resendCode} > Resend Authorisation Code</Text>
                        <Text style={{ marginTop: 40 }}> in {resendButtonDisabledTime} sec</Text>
                    </View>
                </TouchableOpacity >
            )
        }
        <View style={styles.button}>
            <Button buttonTitle="Submit"
                onClick={() =>
                    console.log("otp is ", value)
                } />
        </View>
    </SafeAreaView >
);
}

此屏幕的样式文件在下面的代码中给出:

import { StyleSheet } from 'react-native';
import { Color } from '../../../constants';

export const styles = StyleSheet.create({
root: {
    flex: 1,
    padding: 20,
    alignContent: 'center',
    justifyContent: 'center'
},
title: {
    textAlign: 'left',
    fontSize: 20,
    marginStart: 20,
    fontWeight:'bold'
},
subTitle: {
    textAlign: 'left',
    fontSize: 16,
    marginStart: 20,
    marginTop: 10
},
codeFieldRoot: {
    marginTop: 40,
    width: '90%',
    marginLeft: 20,
    marginRight: 20,
},
cellRoot: {
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
    borderBottomColor: '#ccc',
    borderBottomWidth: 1,
 },
 cellText: {
    color: '#000',
    fontSize: 28,
    textAlign: 'center',
},
focusCell: {
    borderBottomColor: '#007AFF',
    borderBottomWidth: 2,
},

button: {
    marginTop: 20
},
resendCode: {
    color: Color.BLUE,
    marginStart: 20,
    marginTop: 40,
},
resendCodeText: {
    marginStart: 20,
    marginTop: 40,
},
resendCodeContainer: {
    flexDirection: 'row',
    alignItems: 'center'
}
})

希望对很多人有帮助。快乐编码!!


3
投票

我按照 Chethan 的回答解决了 6 位 otp 的问题。 首先在state中创建一个数组'otp',初始化为otp = ['-','-','-','-','-','-'],然后在state中创建一个otpVal字符串

const [otp, setOtp] = useState(['-', '-', '-', '-', '-', '-']);
const [otpVal, setOtpVal] = useState('');

现在渲染 otp 框的实际逻辑如下。

                   <TextInput
                    onChangeText={value => {
                        if (isNaN(value)) {
                            return;
                        }
                        if (value.length > 6) {
                            return;
                        }
                        let val =
                            value + '------'.substr(0, 6 - value.length);
                        let a = [...val];
                        setOtpVal(a);
                        setOtp(value);
                    }}
                    style={{ height: 0 }}
                    autoFocus = {true}
                />
                <View style={styles.otpBoxesContainer}>
                    {[0, 1, 2, 3, 4, 5].map((item, index) => (
                        <Text style={styles.otpBox} key={index}>
                            {otp[item]}
                        </Text>
                    ))}
                </View>

otpBoxesContainer 和 otpBox 的样式如下:

 otpBoxesContainer: {
    flexDirection: 'row'
},
otpBox: {
    padding: 10,
    marginRight: 10,
    borderWidth: 1,
    borderColor: lightGrey,
    height: 45,
    width: 45,
    textAlign: 'center'
}

现在,由于 TextInput 的高度设置为 0,它不会向用户显示,但它仍然接受输入。我们以这样的方式修改和存储该输入,我们可以将其显示为在单独的输入框中输入值。


2
投票

我面临着同样的问题,并且我设法开发了一个很好用的解决方案。忽略提供商,我将其用于我自己的目的,只是为了设置表单值。

行为:

  1. 用户输入第一个密码
  2. 下一个输入已聚焦
  3. 用户删除号码
  4. 号码已删除
  5. 之前的输入已获得焦点

代码

// Dump function to print standard Input field. Mine is a little customised in 
// this example, but it does not affects the logics

const CodeInput = ({name, reference, placeholder, ...props}) => (
  <Input
    keyboardType="number-pad"
    maxLength={1}
    name={name}
    placeholder={placeholder}
    reference={reference}
    textAlign="center"
    verificationCode
    {...props}
  />
);
// Logics of jumping between inputs is here below. Ignore context providers it's for my own purpose.

const CodeInputGroup = ({pins}) => {
  const {setFieldTouched, setFieldValue, response} = useContext(FormContext);
  const references = useRef([]);

  references.current = pins.map(
    (ref, index) => (references.current[index] = createRef()),
  );

  useEffect(() => {
    console.log(references.current);
    references.current[0].current.focus();
  }, []);

  useEffect(() => {
    response &&
      response.status !== 200 &&
      references.current[references.current.length - 1].current.focus();
  }, [response]);

  return pins.map((v, index) => (
    <CodeInput
      key={`code${index + 1}`}
      name={`code${index + 1}`}
      marginLeft={index !== 0 && `${moderateScale(24)}px`}
      onChangeText={(val) => {
        setFieldTouched(`code${index + 1}`, true, false);
        setFieldValue(`code${index + 1}`, val);
        console.log(typeof val);
        index < 3 &&
          val !== '' &&
          references.current[index + 1].current.focus();
      }}
      onKeyPress={
        index > 0 &&
        (({nativeEvent}) => {
          if (nativeEvent.key === 'Backspace') {
            const input = references.current[index - 1].current;

            input.focus();
          }
        })
      }
      placeholder={`${index + 1}`}
      reference={references.current[index]}
    />
  ));
};
// Component caller
const CodeConfirmation = ({params, navigation, response, setResponse}) => {
  return (
    <FormContext.Provider
      value={{
        handleBlur,
        handleSubmit,
        isSubmitting,
        response,
        setFieldTouched,
        setFieldValue,
        values,
      }}>
      <CodeInputGroup pins={[1, 2, 3, 4]} />
    </FormContext.Provider>
  );
};


1
投票

尝试这个包https://github.com/Twotalltotems/react-native-otp-input 它在这两个平台上效果最好


1
投票

尝试这个 npm 包 >>> react-native OTP/确认字段

下面是可用选项的屏幕截图,您的选项属于下划线示例。

下面是下划线示例的代码。

import React, {useState} from 'react';
import {SafeAreaView, Text, View} from 'react-native';

import {
  CodeField,
  Cursor,
  useBlurOnFulfill,
  useClearByFocusCell,
} from 'react-native-confirmation-code-field';

const CELL_COUNT = 4;

const UnderlineExample = () => {
  const [value, setValue] = useState('');
  const ref = useBlurOnFulfill({value, cellCount: CELL_COUNT});
  const [props, getCellOnLayoutHandler] = useClearByFocusCell({
    value,
    setValue,
  });

  return (
    <SafeAreaView style={styles.root}>
      <Text style={styles.title}>Underline example</Text>
      <CodeField
        ref={ref}
        {...props}
        value={value}
        onChangeText={setValue}
        cellCount={CELL_COUNT}
        rootStyle={styles.codeFieldRoot}
        keyboardType="number-pad"
        textContentType="oneTimeCode"
        renderCell={({index, symbol, isFocused}) => (
          <View
            // Make sure that you pass onLayout={getCellOnLayoutHandler(index)} prop to root component of "Cell"
            onLayout={getCellOnLayoutHandler(index)}
            key={index}
            style={[styles.cellRoot, isFocused && styles.focusCell]}>
            <Text style={styles.cellText}>
              {symbol || (isFocused ? <Cursor /> : null)}
            </Text>
          </View>
        )}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
root: {padding: 20, minHeight: 300},
  title: {textAlign: 'center', fontSize: 30},
  codeFieldRoot: {
    marginTop: 20,
    width: 280,
    marginLeft: 'auto',
    marginRight: 'auto',
  },
  cellRoot: {
    width: 60,
    height: 60,
    justifyContent: 'center',
    alignItems: 'center',
    borderBottomColor: '#ccc',
    borderBottomWidth: 1,
  },
  cellText: {
    color: '#000',
    fontSize: 36,
    textAlign: 'center',
  },
  focusCell: {
    borderBottomColor: '#007AFF',
    borderBottomWidth: 2,
  },
})

export default UnderlineExample;

来源:Github 链接到上述代码

希望有帮助! :)


0
投票

有一个插件 React Native Phone Verification 适用于 iOS 和 Android(跨平台),您可以使用符合您要求的验证码选择器。


0
投票

我们曾经使用单个隐藏输入字段来完成此操作,如@Chethan 的答案中所述。现在,RN 已经支持 Android 平台上的后退按钮回调(从 RN 0.58 甚至更早版本开始)。只需使用一组文本输入的正常布局即可做到这一点。但我们还需要考虑 iOS 上的文本输入建议或 Android 上的自动填充。实际上,我们已经开发了一个库来处理这个问题。这里有blog介绍这个库以及如何使用它。源代码是这里


0
投票

@kd12345:您可以在以下位置进行操作:

onChangeText={(val) => {
  setFieldTouched(`code${index + 1}`, true, false);
  setFieldValue(`code${index + 1}`, val);
  console.log(typeof val);
  // LITTLE MODIFICATION HERE
  if(index < 3 && val !== '') {
    references.current[index + 1].current.focus();
    // DO WHATEVER
  }
          
}}

0
投票

为此,你应该使用 react-native-split-input

© www.soinside.com 2019 - 2024. All rights reserved.