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

5
我的页面只有一个单独的,我已经传入了autoFocus prop: autoFocus:true
<TextInput
   style={styles.textInput}
   placeholder="Quiz Deck Title"
   autoFocus={true}
   value={this.state.title}
   onChangeText={(title) => this.controlledTextInput(title)}
/>

我想要避免的是需要用户在TextInput框中“点击”才能弹出键盘。
但是,如果设备没有硬件键盘,我也希望软键盘自动打开。
在React Native中有没有办法实现这一点?我正在为iOS和Android开发。
如果有影响的话,我的页面是通过TabNavigator导航到的。
我提到这一点是因为对另一个类似的SO问题的评论(见下文)表明,当他们使用StackNavigator到达页面时,他们也遇到了类似的问题。 关于类似的SO问题的说明:
如何在React Native中自动打开键盘? :没有提供解决方案,其他人的评论表明与我得到的结果相同:输入已聚焦,但键盘不会自动打开。
关闭/隐藏Android软键盘 Android:当焦点在EditText上时自动显示软键盘 :使用本地android代码(java),而不是React Native代码(javascript)。
注意:我正在使用推荐的安卓23版本的安卓模拟器Nexus 6P和iPhone 6s上的iOS模拟器进行开发,因为我没有实体设备。
编辑:添加请求代码
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.jsstyles 定义

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即可打开它。 - Matt Aft
@MattAft - 是的,我确实找到了。很高兴你提到了这一点,为了未来的访问者,因为这不是一个明显的要求。 - SherylHohman
@MattAft 目前在这两个平台上,用户必须先“点击”文本字段,键盘才会显示出来。当我使用像这样的应用程序时,我觉得这是一个令人烦恼的特点。我更希望的是,在页面加载时键盘自动弹出。 - SherylHohman
当组件加载时,您可以通过引用将焦点添加到任何“textinput”,以显示键盘。 - Pritish Vaidya
1
@MattAft 我说错了:在Android中键盘不会自动弹出,但在iOS中它会弹出(cmd-k已启用)。 - SherylHohman
显示剩余2条评论
4个回答

6

只需在timeout中包装ref即可 setTimeout(()=>{this.textInputRef.focus()},100)


2
你知道为什么需要这样做吗? - Saad
我猜这个小延迟是为了在调用focus()之前给ref赋值一些时间。 - comphonia
您解决了我的难题,非常感谢您发布这个解决方案。 - Vishali

5

虽然这个问题已经存在一段时间了,但如果有人在这里搜索答案..

看起来,如果你被困在动画中(比如从另一个屏幕返回),键盘就不会弹出。

你可以使用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)}
/>

文档所述

通过原生元素公开的两种方法是 .focus().blur(),它们可以在程序中自动聚焦或模糊TextInput。


2
不幸的是,这给了我相同的行为:用户必须在输入框中“点击”才能弹出键盘。 - SherylHohman
页面中包含了 KeyboardAvoidingView 组件,以及一个 Text 和一个 TouchableOpacity(提交按钮)组件。就是这些。可能是 KeyboardAvoidingView 组件在干扰吗? - SherylHohman
你可以更新问题并附上相关代码,另外你使用的是哪个 TabNavigator - Pritish Vaidya
好的。不,除非我点击TextInput框,否则它不会聚焦。当我使用键盘输入时,除非我已经在输入框内点击过一次,否则不会显示任何内容。 - SherylHohman
我已经添加了styles,导入和StylesButton代码,所以当你有时间查看代码时,它们就可以使用。感谢你迄今为止的帮助。 - SherylHohman
显示剩余6条评论

0

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