组件挂载时如何在 React Native 中自动打开键盘?

Posted

技术标签:

【中文标题】组件挂载时如何在 React Native 中自动打开键盘?【英文标题】:How to open keyboard automatically in React Native when component mounts? 【发布时间】:2018-10-03 23:55:34 【问题描述】:

我的页面只有一个TextInput,我已经传入了autoFocus prop:autoFocus: true

<TextInput
   style=styles.textInput
   placeholder="Quiz Deck Title"
   autoFocus=true
   value=this.state.title
   onChangeText=(title) => this.controlledTextInput(title)
/>

我要避免的是要求用户在键盘弹出之前“单击”TextInput 框。

但我希望软键盘也自动打开(如果设备没有硬件键盘)。

有没有办法在本机反应中实现这一点?我正在为 iosandroid 开发。

如果重要,我的页面将通过 TabNavigator 导航到。我之所以提到这一点,是因为对另一个类似 SO 问题的评论(见下文)表明他们在使用 StackNavigator 到达他们的页面时遇到了类似的问题.

关于类似 SO 问题的注意事项:How to open keyboard automatically in React Native? :没有提供解决方案,其他人的 cmets 建议的结果与我相同:输入已聚焦,但键盘不会自动打开。Close/hide the Android Soft Keyboard 和Android: show soft keyboard automatically when focus is on an EditText :正在使用本机android代码(java),而不是反应本机代码(javascript)。

注意:我正在使用 android 模拟器 Nexus 6P 和 android 23(推荐)和 ios 模拟器和 iPhone 6s 进行开发,因为我没有物理设备。

编辑:添加请求的代码

NewDeck.js(我希望键盘自动弹出的视图):

import React from 'react';
import  connect  from 'react-redux';
import  View, Text, TouchableOpacity,
         TextInput, KeyboardAvoidingView,
         StyleSheet, Platform,
        from 'react-native';

import StyledButton from '../components/StyledButton';

import  saveDeck       from '../store/decks/actionCreators';
import  getDeckList    from '../store/decks/selectors';
import  fetchDecks     from '../utils/api';
import  saveDeckTitle  from '../utils/api';
        from '../utils/colors';
import  titleCase, stripInvalidChars, makeStringUnique 
  from '../utils/helpers';
import  white, gray, primaryColor, primaryColorDark, primaryColorLight,

class NewDeck extends React.Component     
  state = 
    title: '',
    canSubmit: false,
      
  componentDidMount () 
    this.textInputRef.focus()
  
  controlledTextInput(title)
    title = titleCase(stripInvalidChars(title));
    const canSubmit = this.isValidInput(title);
    this.setState( title, canSubmit );
      
  isValidInput(text)
    return text.trim() !== '';
  
  onBlur()
    title = this.state.title.trim();
    const unique = makeStringUnique(title, this.props.existingTitles);
    this.setState( title: unique );
      
  onSubmit()
    let title = this.state.title.trim();    
    title = makeStringUnique(title, this.props.existingTitles)
    saveDeckTitle(title)    
    this.props.navigation.navigate('Home');
      
  render() 
      return (
          <View style=styles.container>
            <View style=[styles.cardContainer, flex: 1]>
              <Text  style=styles.instructionsText
                >
                Title for your New Quiz Deck
              </Text>

              <KeyboardAvoidingView ...keyboardAvoidingViewProps>
                <TextInput
                  style=styles.textInput
                  placeholder="Quiz Deck Title"
                  value=this.state.title
                  onChangeText=(title) => this.controlledTextInput(title)
                  /* autoFocus=true */
                  ref=ref => this.textInputRef = ref
                  />
              </KeyboardAvoidingView>
            </View>

            <KeyboardAvoidingView
              ...keyboardAvoidingViewProps
              style=[styles.buttonsContainer, styles.buttonContainer]
              >
              <StyledButton
                style=[styles.item, style=flex: 2]
                onPress=() => this.onSubmit()
                disabled=!this.state.canSubmit
                >
                <Text>
                  Submit
                </Text>
              </StyledButton>
            </KeyboardAvoidingView>
          </View>
      );
  


const keyboardAvoidingViewProps = 
  behavior: 'padding',
;

// ...styles definition here, - I posted it in a later code block, to minimize 
// clutter, in the event that it is irrelevant to this issue

function mapStoreToProps(store)
  const decks  = getDeckList(store) || null;
  // ensure titles are unique (better UX than if just make id unique)
  const existingTitles = decks && decks.map(deck => 
    return deck.title
  ) || [];
  return 
    existingTitles,
  

export default connect(mapStoreToProps)(NewDeck);

TabNavigatorStackNavigator 代码(在 App.js 中):

// ... the TabNavigator I'm using:
import  TabNavigator, StackNavigator  from 'react-navigation';

//... the class's render method, uses StackNavigator (as MainNavigation)
render()
    return (
      <Provider store=createStore(rootReducer)>
        <View style=flex:1>
          <AppStatusBar
            backgroundColor=primaryColor
            barStyle="light-content"
            />
          <MainNavigation />
        </View>
      </Provider>
    );
  


// ... 
const Tabs = TabNavigator(
  
    DeckList: 
      screen: DeckList,
      navigationOptions: 
        tabBarLabel: 'Quiz Decks',
        tabBarIcon: ( tintColor ) =>  // icons only show in ios
          <Ionicons name='ios-bookmarks' size=30 color=tintColor />
      ,
    ,
    NewDeck: 
      screen: NewDeck,
      navigationOptions: 
        tabBarLabel: 'Create New Deck',
        tabBarIcon: ( tintColor ) => // icons only show in ios
          <FontAwesome name='plus-square' size=30 color=tintColor />
      ,
    ,
  ,
  
    navigationOptions: 
      // do-not-display page headers for Tab Navigation
      header: null
    ,    
    tabBarOptions: 
      // ios icon and text color; android text color
      activeTintColor:   Platform.OS === 'ios' ? primaryColor : white,    
      pressColor: white,    
      indicatorStyle: 
        backgroundColor: primaryColorDark,
        height: 3,
      ,    
      style: 
        height: 56,
        backgroundColor: Platform.OS === 'ios' ? white  : primaryColor,    
        shadowColor: 'rgba(0, 0, 0, 0.24)',
        shadowOffset: 
          width: 0,
          height: 3
        ,
        shadowRadius: 6,
        shadowOpacity: 1
      
    
  
);

//... StackNavigator uses TabNavigator (as Tabs)
const stackScreenNavigationOptions = 
  headerTintColor:   white,
  headerStyle: 
    backgroundColor: primaryColor,
  
;
const MainNavigation = StackNavigator(
  //  RouteConfigs: This is analogous to defining Routes in a web app
  
    Home: 
      screen: Tabs,  // Which also loads the first Tab (DeckList)
    ,
    Deck: 
      screen: Deck,
      navigationOptions: stackScreenNavigationOptions,
    ,
    Quiz: 
      screen: Quiz,
      navigationOptions: stackScreenNavigationOptions,
    ,
    NewDeck: 
      screen: NewDeck,
      navigationOptions: stackScreenNavigationOptions,
    ,
    NewCard: 
      screen: NewCard,
      navigationOptions: stackScreenNavigationOptions,
    ,
  ,
);

这是 NewDeck.js

styles 定义
const styles = StyleSheet.create(
  // CONTAINER styles
  wrapper: 
    // this was the previous container style
      flex: 1,
      backgroundColor: white,
      alignItems: 'center',
      justifyContent: 'center',
    ,
  container: 
    flex: 1,
    backgroundColor: white,
    alignItems: 'center',
    justifyContent: 'space-between',

    padding: 10,
    paddingTop: 30,
    paddingBottom: 5,
  ,
  cardContainer: 
    flex: 1,
    justifyContent: 'flex-start',
    alignSelf: 'stretch',
    backgroundColor: '#fefefe',

    padding:     20,
    marginLeft:  30,
    marginRight: 30,
    marginTop:   10,
    borderRadius: Platform.OS === 'ios' ? 20 : 10,

    shadowRadius: 3,
    shadowOpacity: 0.8,
    shadowColor: 'rgba(0, 0, 0, 0.24)',
    shadowOffset: 
      width: 0,
      height: 3,
    ,
    marginBottom:20,
  ,
  buttonsContainer: 
    flex: 3,
    alignSelf: 'stretch',
    justifyContent: 'flex-start',
  ,
  buttonContainer: 
    justifyContent: 'center',
    margin: 10,
  ,

  // TEXT Styles
  instructionsText: 
    flex: 1,
    fontSize: 20,
    color: gray,

    alignSelf: 'center',
    textAlign: 'center',
  ,

  // INPUTTEXT styles
  textInput: 
    fontSize: 27,
    color: primaryColor,

    alignSelf: 'stretch',
    flexWrap:  'wrap',
    textAlign: 'center',
    marginTop: 10,
  ,
);

StyledButton.js(基本上,TouchableOpacity 具有特定于平台的样式,可在整个应用程序中普遍使用):

import React from 'react';
import  Text, TouchableOpacity, StyleSheet, Platform  from 'react-native';
import  white, gray, primaryColor, primaryColorLight, primaryColorDark from '../utils/colors';

export default function TextButton( children, onPress, customColor, disabled=false ) 
  const disabledColor = disabled ? gray : null;
  const backgroundColor = Platform.OS==='ios' ? white : disabledColor || customColor || primaryColorLight;
  const borderColor     = Platform.OS==='ios' ? disabledColor || customColor || primaryColorDark
  const textColor       = Platform.OS==='ios' ? disabledColor || customColor || primaryColor  : white;
  const btnStyle = Platform.OS==='ios' ? styles.iosBtn : styles.androidBtn;
  const txtStyle = styles.txtDefault;
  const btnColor =  backgroundColor, borderColor ;
  const txtColor =  color: textColor ;

  return (
      <TouchableOpacity
        onPress=onPress
        disabled=disabled
        style=[btnStyle, btnColor]
        >
        <Text
          style=[styles.txtDefault, txtColor]
          >
          children
        </Text>
      </TouchableOpacity>
  );


const styles = StyleSheet.create(
  txtDefault: 
    textAlign: 'center',
    // because of bleeding of white text to colored background on android,
    // enlarge text (or increase fontWeight) for better readability
    fontSize: Platform.OS==='ios' ? 15 : 18,
    padding: 10,
  ,
  iosBtn: 
    height: 45,
    borderRadius: 7,
    alignSelf:      'center',
    justifyContent: 'center',
    alignItems:     'center',
    // ios only settings
    borderColor:   primaryColorDark,
    borderWidth:   1,
    borderRadius:  3,
    paddingLeft:  25,
    paddingRight: 25,
  ,
  androidBtn: 
    height: 45,
    borderRadius: 5,
    alignSelf:      'center',
    justifyContent: 'center',
    alignItems:     'center',
    // android- only settings
    // (padding accepts clicks, vs. margin === no-click zone)
    padding: 20,
    paddingLeft:  15,
    paddingRight: 15,
  ,
);

// ios has white buttons with colored outlines and colored text
// android has colored buttons with white text
// Pass in a button color, or it defaults to the App's primary colors

【问题讨论】:

如果您只在模拟器上进行过测试,那么您可能只是默认隐藏了键盘。在ios模拟器中,按cmd + k 开启。 @MattAft - 是的,我确实碰巧找到了。很高兴您为未来的访问者提及它,因为这不是一个明显的要求。 @MattAft 目前在两个平台上,用户都需要在键盘出现之前“单击”文本字段。当我使用这样的应用程序时,我发现这是一个令人讨厌的功能。我更喜欢的是,当页面加载时键盘会自动弹出。 您可以在组件加载时将焦点添加到任何textinput 以使用它的引用显示键盘 @MattAft 我错了:键盘不会在 Android 中自动弹出,但在 iOS 中会弹出(cmd-k 已打开)。 【参考方案1】:

只需将 ref 包裹在 timeout 内 setTimeout(()=&gt;this.textInputRef.focus(),100)

【讨论】:

你知道为什么需要这样做吗? 我猜这个小小的延迟给了在调用 focus() 之前实际分配 ref 的时间。【参考方案2】:

老问题,但如果有人在这里搜索答案..

如果您卡在动画中(例如,如果来自另一个屏幕),键盘似乎不会出现。

您可以使用ref 专注于您的输入,但必须包含在InteractionManager.runAfterInteractions 中才能正常工作。

我就是这样解决的:

export const CustomInput: FunctionComponent<Props & TextInputProps> = (
  error,
  ...props
) => 
  const inputRef = useRef<TextInput>(null);

  useEffect(() => 
    // Must run after animations for keyboard to automatically open
    InteractionManager.runAfterInteractions(() => 
      if (inputRef?.current) 
        inputRef.current.focus();
      
    );
  , [inputRef]);

  return (
    <View>
      <TextInput
        ref=inputRef
        ...props
      />
      error && <ErrorText>error</ErrorText>
    </View>
  );
;```

【讨论】:

【参考方案3】:

您始终可以在任何TextInput 上使用.focus当组件加载时,如果您想避免autoFocus,则显示键盘。

componentDidMount () 
    this.textInputRef.focus()


<TextInput
    style=styles.textInput
    ref=ref => this.textInputRef = ref
    placeholder="Quiz Deck Title"
    autoFocus=true
    value=this.state.title
    onChangeText=(title) => this.controlledTextInput(title)
/>

如docs中所述

通过本机元素公开的两个方法是 .focus().blur(),它们将以编程方式聚焦或模糊 TextInput。

【讨论】:

不幸的是,这给了我同样的行为:用户必须在键盘弹出之前“点击”输入框。 好的,当你加载同一个组件时,textInput 是焦点还是 unfocused 嗯..好问题。光标未显示在 TextInput 框内(直到我在 TextInput 内单击),所以我怀疑它实际上不是“聚焦”。这对于您的代码和我发布的代码都是一样的。 可能有一些overlay 或其他东西正在使用ref focus 当你使用自动对焦时,它基本上会在组件安装后调用焦点,所以它是完全相同的东西。如果您键入并且它显示在 textinput 上,那么您就会知道它已获得焦点。【参考方案4】:

我现在只需要“autoFocus”道具就可以做到这一点。

https://reactnative.dev/docs/textinput#autofocus

【讨论】:

以上是关于组件挂载时如何在 React Native 中自动打开键盘?的主要内容,如果未能解决你的问题,请参考以下文章

React Native组件生命周期

(React、MaterialUI、Context)MUI 自动完成/TextField 未在挂载时呈现其值

如何使用 redux 让根组件在 react-native 中重新渲染(开源项目)

如何在 React Native 中的组件之间传递数据

如何在组件 React / React-native 的单独页面中使用动态 graphql(字符串)

如何在 React-Native 组件的警报中显示 state