如何在React Native中实现下一个TextInput自动聚焦

19

我正在尝试创建一个带密码保护的屏幕,屏幕将使用4个数字输入作为密码。

我的做法是创建一个TextInput组件,并在主屏幕中调用它4次。

我遇到的问题是,当我键入前一个TextInput的值时,TextInputs不会自动聚焦到下一个TextInput。

对于所有PasscodeTextInput组件,我都使用refs(我被告知这是一种过时的方法,但我不知道其他方法)。

尝试了这种方法(不创建自己的组件),也没有成功。 方法

图片

index.ios.js

import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';

export default class ProgressBar extends Component {
  render() {
    const { centerEverything, container, passcodeContainer,  textInputStyle} = styles;
    return (
      <View style={[centerEverything, container]}>
        <View style={[passcodeContainer]}>
          <PasscodeTextInput
            autoFocus={true}
            ref="passcode1"
            onSubmitEditing={(event) => { this.refs.passcode2.focus() }} />
          <PasscodeTextInput
            ref="passcode2"
            onSubmitEditing={(event) => { this.refs.passcode3.focus() }} />
          <PasscodeTextInput
            ref="passcode3"
            onSubmitEditing={(event) => { this.refs.passcode4.focus() }}/>
          <PasscodeTextInput
            ref="passcode4" />
        </View>
      </View>
    );
  }
}

const styles = {
  centerEverything: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  container: {
    flex: 1,
    backgroundColor: '#E7DDD3',
  },
  passcodeContainer: {
    flexDirection: 'row',
  },
}

AppRegistry.registerComponent('ProgressBar', () => ProgressBar);

PasscodeTextInput.js

import React from 'react';
import {
  View,
  Text,
  TextInput,
  Dimensions
} from 'react-native';

const deviceWidth = require('Dimensions').get('window').width;
const deviceHeight = require('Dimensions').get('window').height;

const PasscodeTextInput = ({ ref, autoFocus, onSubmitEditing, onChangeText, value}) => {

  const { inputStyle, underlineStyle } = styles;

  return(
    <View>
      <TextInput
        ref={ref}
        autoFocus={autoFocus}
        onSubmitEditing={onSubmitEditing}
        style={[inputStyle]}
        maxLength={1}
        keyboardType="numeric"
        placeholderTextColor="#212121"
        secureTextEntry={true}
        onChangeText={onChangeText}
        value={value}
      />
      <View style={underlineStyle} />
    </View>
  );
}

const styles = {
  inputStyle: {
    height: 80,
    width: 60,
    fontSize: 50,
    color: '#212121',
    fontSize: 40,
    padding: 18,
    margin: 10,
    marginBottom: 0
  },
  underlineStyle: {
    width: 60,
    height: 4,
    backgroundColor: '#202020',
    marginLeft: 10
  }
}

export { PasscodeTextInput };

Update 1

index.ios.js

import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';

export default class ProgressBar extends Component {

  constructor() {
    super()
    this.state = {
      autoFocus1: true,
      autoFocus2: false,
      autoFocus3: false,
      autoFocus4: false,
    }
  }

  onTextChanged(t) { //callback for immediate state change
    if (t == 2) { this.setState({ autoFocus1: false, autoFocus2: true }, () => { console.log(this.state) }) }
    if (t == 3) { this.setState({ autoFocus2: false, autoFocus3: true }, () => { console.log(this.state) }) }
    if (t == 4) { this.setState({ autoFocus3: false, autoFocus4: true }, () => { console.log(this.state) }) }
  }

  render() {
    const { centerEverything, container, passcodeContainer, testShit, textInputStyle } = styles;
    return (
      <View style={[centerEverything, container]}>
        <View style={[passcodeContainer]}>
          <PasscodeTextInput
            autoFocus={this.state.autoFocus1}
            onChangeText={() => this.onTextChanged(2)} />
          <PasscodeTextInput
            autoFocus={this.state.autoFocus2}
            onChangeText={() => this.onTextChanged(3)} />
          <PasscodeTextInput
            autoFocus={this.state.autoFocus3}
            onChangeText={() => this.onTextChanged(4)} />
          <PasscodeTextInput
            autoFocus={this.state.autoFocus4} />
        </View>
      </View>
    );
  }
}

const styles = {
  centerEverything: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  container: {
    flex: 1,
    backgroundColor: '#E7DDD3',
  },
  passcodeContainer: {
    flexDirection: 'row',
  },
}

AppRegistry.registerComponent('ProgressBar', () => ProgressBar);

这是关于React Native的信息。希望能对你有所帮助。 - Ujjwal Nepal
@UjjwalNepal,focus()方法已经被弃用了...而mitch的答案在0.40之后就不能使用了。https://dev59.com/VlwY5IYBdhLWcg3wVGeB#41201939 - J.Doe
我想你可以完全避免在父组件中使用状态,只需在 componentDidMount 中聚焦第一个输入,并且你的 onTextChanged 方法可以像这样:if t == 1 or 2 or 3 then focus the t+1'th input - Igorsvee
@Igorsvee 怎样在 componentDidMount 上做聚焦?在 componentDidMount 中管理 state 让人望而生畏,能否分享一些代码? - J.Doe
1
componentDidMount被调用后,它意味着元素已经被渲染,我们现在可以通过refs访问它们:this.refs.passcode1.focus() - Igorsvee
添加此 autoFocus={true} - Omar bakhsh
8个回答

22

在TextInput中有一个defaultProp,可以在组件装载后进行聚焦。

autoFocus

如果为true,则在componentDidMount上聚焦输入, 默认值为false。更多信息请阅读相关的文档

更新

componentDidUpdate 后,它将无法正常工作。在这种情况下,可以使用ref进行编程式地聚焦。


你的答案是正确的,但在 componentDidUpdate 之后它没有正常工作。 - AmerllicA
1
在这种情况下,可以使用 ref 来进行程序化的聚焦。这只是一个选项。 - Mukundhan

16

您不能使用该方式将ref转发给<TextInput>,因为ref特殊属性之一。因此,调用this.refs.passcode2将会返回<PasscodeTextInput>

请尝试更改如下内容以从<TextInput>获取ref

PasscodeTextInput.js

const PasscodeTextInput = ({ inputRef, ... }) => {

  ...

  return (
    <View>
      <TextInput
        ref={(r) => { inputRef && inputRef(r) }}
        ...
      />
    </View>
    ...
  );
}

然后,将中的分配给一个变量,并使用切换焦点(在RN 0.41.2中未被弃用)。

index.ios.js

return (
  <PasscodeTextInput
    autoFocus={true}
    onChangeText={(event) => { event && this.passcode2.focus() }} />

  <PasscodeTextInput
    inputRef={(r) => { this.passcode2 = r }}
    onChangeText={(event) => { event && this.passcode3.focus() }} />

  <PasscodeTextInput
    inputRef={(r) => { this.passcode3 = r }}
    onChangeText={(event) => { event && this.passcode4.focus() }} />

  <PasscodeTextInput
    inputRef={(r) => { this.passcode4 = r }} />
);

P.S:event && this.passcode2.focus() 防止在尝试清除旧密码并输入新密码时切换焦点。


@J.doe:谢谢你的代码。你移除了按下删除键的功能吗? - djk
这个方法救了我的命,而且比那些像forwardRef这样的技术更简单。但我仍然希望React Native未来能够本地处理焦点移动。 - Jonathan

7
我们采用了不同的方法处理这种类型的屏幕。
与其管理4个单独的TextInput并处理焦点在每个TextInput之间的导航(以及用户删除字符后再返回),我们在屏幕上仅有一个TextInput,但它是不可见的(即0px x 0px),它具有焦点、maxLength和键盘配置等。
这个TextInput接收用户输入,但实际上看不到它,因为每输入一个字符,我们都将输入的文本呈现为一系列简单的View/Text元素,样式与您屏幕上的类似。
这种方法对我们很有效,无需管理“下一个”或“上一个”TextInput来聚焦。

甜蜜的解决方案 :) - philwilks

2
你可以像Jason所说的那样使用焦点方法onChangeText,此外添加maxLength={1}可以使你立即跳转到下一个输入框,而无需检查添加了什么内容。(我刚注意到它已经不推荐使用了,但这就是我解决问题的方法,应该在v0.36之前还可以使用。link解释了如何更新被弃用的函数。)
  <TextInput
   ref="first"
   style={styles.inputMini}
   maxLength={1}
   keyboardType="numeric"
   returnKeyType='next'
   blurOnSubmit={false}
   placeholderTextColor="gray"
   onChangeText={(val) => {
      this.refs['second'].focus()
   }}
  />
  <TextInput
   ref="second"
   style={styles.inputMini}
   maxLength={1}
   keyboardType="numeric"
   returnKeyType='next'
   blurOnSubmit={false}
   placeholderTextColor="gray"
   onChangeText={(val) => {
     this.refs['third'].focus()
   }}
  />
  ...

请注意,我的引用使用已经过时,但我只是复制了代码,因为我可以保证那时它是有效的(希望现在也有效)。
最后,这种实现方式的主要问题是,一旦你尝试用退格键删除一个数字,你的焦点将跳到下一个数字,导致严重的用户体验问题。然而,您可以监听退格键输入并执行不同的操作,而不是将焦点聚焦到下一个输入框。所以,我会在这里留下一个链接,让您进一步研究,如果您选择使用这种实现方式。
先前描述问题的hacky解决方案:如果在执行任何操作之前检查onChangeText属性中输入的内容,如果该值是数字,则可以跳转到下一个输入框,否则(即退格键),则跳回上一个输入框。(我刚想出这个主意,还没有尝试过。)

1
我认为问题在于onSubmitEditing是指当您在常规键盘上按下“return”或“enter”键时......但是在小键盘上没有这些按钮。

假设您希望每个输入框只有一个字符,您可以查看onChangeText,然后检查文本是否长度为1,如果确实为1,则调用聚焦。


我也这么想,我对我的代码进行了一些更改,但它仍然无法工作。 - J.Doe

1
    <TextInput 
    ref={input => {
          this.nameOrId = input;
        }}
    />



    <TouchableOpacity
      onPress={()=>{
        this.nameOrId.focus()
      }}
     >
      <Text>Click</Text>
    </TouchableOpacity>

0
通过移除 autoFocus={true} 并设置 timeout,已解决此问题。 我有一个弹出窗口作为功能组件,并使用 Refs 来使用 "current.focus()",如下所示:
   const Popup = ({   placeholder,   autoFocus,   showStatus, }) => {  const inputRef = useRef(null);   useEffect(() => {
     Platform.OS === 'ios'
        ? inputRef.current.focus()
        : setTimeout(() => inputRef.current.focus(), 40);   }, [showStatus]);   return (
    <View style={styles.inputContainer}>
      <TextInput
        style={styles.inputText}
        defaultValue={placeholder}
        ref={inputRef}
      />
    </View>  };

0
我用以下代码解决了这个问题: const VerifyCode: React.FC = ({ pass, onFinish }) => {
      const inputsRef = useRef<Input[] | null[]>([]);
      const [active, setActive] = useState<number>(0);

      const onKeyPress = ({ nativeEvent }: 
      NativeSyntheticEvent<TextInputKeyPressEventData>) => {
       if (nativeEvent.key === "Backspace") {
        if (active !== 0) {
          inputsRef.current[active - 1]?.focus();
          return setActive(active - 1);
        }
       } else {
          inputsRef.current[active + 1]?.focus();
          return setActive(active + 1);
       }
      return null;
     };

   return (
    <View style={styles.container}>
      <StyledInput
        onKeyPress={onKeyPress}
        autoFocus={active === 0}
        ref={(r) => {
          inputsRef.current[0] = r;
        }}
      />
      <StyledInput
        onKeyPress={onKeyPress}
        autoFocus={active === 1}
        ref={(r) => {
          inputsRef.current[1] = r;
        }}
      />
      <StyledInput
        onKeyPress={onKeyPress}
        autoFocus={active === 2}
        ref={(r) => {
          inputsRef.current[2] = r;
        }}
      />
      <StyledInput
        onKeyPress={onKeyPress}
        autoFocus={active === 3}
        ref={(r) => {
          inputsRef.current[3] = r;
        }}
      />
    </View>
 );
};

export default VerifyCode;

我在所有输入框中放置了一个引用,当 onKeyPress 触发时,该函数会验证是否需要返回或前往下一个输入框。


嗨,尝试解释为什么你的解决方案有效。这会使理解更容易。 - Nisanth Reddy

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