如何设计React Native的OTP输入界面?

12

我刚开始学习React Native的设计。请告诉我如何实现下面展示的屏幕图片描述

需要使用4个吗?还是只需要一个就可以了?

9个回答

5
您可以使用一个隐藏的TextInput元素,并附加一个onChangeText函数,以填充在文本视图中输入的值(如果设计需要,您可以使用四个不同的文本视图)。 确保在用户单击它时将焦点放在TextInput上。

嘿,你能详细解释一下你的方法吗?最好附上例子。我不是要求代码,而是要求解释。 - Rajendran Nadar

5

我创建了一个屏幕,包括六个文本输入框,用于OTP验证,具有重新发送OTP功能和90秒的计时器。在Android和iOS上进行全面测试。enter image description here

enter image description here

我使用了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的问题。首先在状态中创建一个初始化为otp = ['-','-','-','-','-','-']的数组'otp',然后创建一个名为'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,它不会显示给用户,但仍然可以接收输入。我们以一种特定的方式修改和存储该输入,以便我们可以像值已经输入到单独的输入框中一样展示它们。

只需要进行一个小改动,任何人都可以使用这个。只需在textInput中添加以下内容:style={{ color:'transparent',height: 50,width:'100%',position:'absolute',backgroundColor:'transparent' }} caretHidden={true}将backgroundColor更改为其他颜色以查看隐藏的逻辑祝大家编码愉快! - Ankit

2

我曾经面临同样的问题,但是我成功地开发出了一个完美运行的解决方案。忽略“provider”,我只是为了设置表单值而使用它。

行为:

  1. 用户输入第一个PIN码
  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>
  );
};


嗨,当用户输入所需数字时如何自动提交? - kd12345

1

实际上并不是最好的,它有很多问题。 - Kailash Uniyal
我也是。每当我尝试集中精力到输入框时,它立即失焦。 - bjornl

1

请尝试使用这个npm包 >>> React Native OTP /确认字段

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

example options

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

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链接

希望能有所帮助! :)


2
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。- 来自审查 - Daemon Painter
谢谢,我已经更新了答案!但是答案被踩了 :/ - Prem G
嗨,当用户输入所需数字时如何自动提交? - kd12345

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

我们以前是按照@Chethan的回答中描述的使用单个隐藏输入字段来完成它。现在,由于RN已经支持在Android平台上的返回按钮上进行回调(自RN 0.58甚至更早以来)。只需使用一组文本输入的普通布局即可实现此操作。但是,我们还需要考虑iOS上的文本输入建议或Android上的自动填充。实际上,我们开发了一个库来处理这个问题。这里是博客介绍该库以及如何使用它。源代码在这里


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接