以前TextInput autoFocus on backspace按react-native android

时间:2017-09-01 08:11:51

标签: react-native

我想将光标焦点移动到屏幕上的前一个textInput上。 onkeyPress无法正常工作,因为该功能仅适用于IOS平台,不适用于Android。那么如何在Android平台中阻止这种方法呢?

TextInput的示例代码:

          <TextInput style = {styles.input}
            keyboardType='numeric'
            maxLength={1}
            ref="otpcode1"
            onChangeText={(event) => {
              this.setState({otp1: event});
              this.refs.otpcode2.focus() }}
            underlineColorAndroid='#fff'/>

          <TextInput style = {styles.input}
            keyboardType='numeric'
            maxLength={1}
            ref="otpcode2"
            onChangeText={(event) => {
              this.setState({otp2: event});
              this.refs.otpcode3.focus() }}
            underlineColorAndroid='#fff'/>

          <TextInput style = {styles.input}
            keyboardType='numeric'
            maxLength={1}
            ref="otpcode3"
            onChangeText={(event) => {
              this.setState({otp3: event});
              this.refs.otpcode4.focus() }}
            underlineColorAndroid='#fff'/>

         <TextInput style = {styles.input}
            keyboardType='numeric'
            maxLength={1}
            ref="otpcode4"
            onChangeText={(event) => {
              this.setState({otp4: event});
              console.log(this.state) }}
            underlineColorAndroid='#fff'/>

TextInput view screen example

2 个答案:

答案 0 :(得分:2)

您应该只使用onKeyPress中的TextInput道具,并检测keyValue是BackSpace的时间,以专注于先前的TextInput,诸如此类:

onKeyPress在最新版本的React-Native上可在iOS和Android上运行,因此以下示例应可运行:

class Test extends Component {

    constructor() {
        super();

        this.handleKeyPress = this.handleKeyPress.bind(this);
    }

    render() {
        return (
            <TextInput
                onKeyPress={ this.handleKeyPress }
            />
        )
    }

    handleKeyPress({ nativeEvent: { key: keyValue } }) {
        if (keyValue === 'Backspace') {
            this.refs.refOfPreviousInput.focus();
        }
    }
}

但是您使用的是老版本的react-native,您可以在onChangeTextonChange中处理它,只需检查您的值length,如果是为0时将焦点放在先前的输入上:

class Test extends Component {

    constructor() {
        super();

        this.handleKeyPress = this.handleKeyPress.bind(this);
    }

    render() {
        return (
            <TextInput
                onChangeText={ this.handleChangeText }
            />
        )
    }

    handleChangeText(value) {
        if (value.length === 0) {
            this.refs.refOfPreviousInput.focus();
        }
    }
}

这不是完整的代码,但是向您展示了如何处理它。 希望对您有所帮助!

答案 1 :(得分:0)

有同样的问题,做了一个肮脏的解决方案,但它的工作原理 逻辑和问题的解释都在代码中。

用法:

import {Alert} from 'react-native';

onPinEntered = (pin) => {
    Alert.alert(
      'Entered pin',
      pin,
      [{text: 'OK'}]
    )
}

render() {
    return (
        <PinInput onPinEntered={this.onPinEntered}/>
    )
}

成分:

import React, {PureComponent} from 'react';
import {
    Platform,
    StyleSheet,
    TextInput,
    View,
    Text,
    TouchableWithoutFeedback,
    Keyboard,
    Animated
} from 'react-native';
import PropTypes from 'prop-types';
// import ui_size from '../tools/UISizer';

let ui_size = function(points){
    //simple sizing function (it project it handles complex screen adaptation)
    return points*2
}

//Main technical problems:
//1. At date of development RN doesn't provide onKeyPress for android platform
//2. Changing text and selection of TextInput at same time is extremely buggy
//Solution:
//Create TextInput with maxLength = 2 and always prefilled with empty_character = ' ', (value={empty_character})
//add self-written cursor pointer
//track changes of text, if new text is '', backspace was pressed - move cursor to left by 1
//                       if new text is not empty, add new character to buffer (state.pin), mover cursor to right by 1
//Some times during reloads, selection is setting before defaultValue of TextInput and raises setSpan error (at least on Android)
//      so additionally track focused state (in render this.input.isFocused() raises strange error:
//                                           "TypeError owner.getName is not a function")
//On android, when input is focused and user hides keyboard by back button, it can only be shown by tap on input
//                    (.focus() don't show keyboard if input is already focused), so track it and blur the input

const empty_character = ' '

export default class PinInput extends PureComponent {

    static defaultProps = {
        maxChars: 4,
        inputProps: {},
    };

    static propTypes = {
        maxChars: PropTypes.number,
        onPinEntered: PropTypes.func,
        inputProps: PropTypes.object,
    };

    constructor(props) {
        super(props);
        this.state = {
            pin: '', // entered pin
            selection: undefined,  // cursor position
            isFocused: false,
            blinkAnim: new Animated.Value(1),
            isLastCharUpdated: false, // track if last character was updated
        }
    }

    onPressCell = (event, id) => {
        let {pin} = this.state
        // by pressing on unfilled cell, set cursor next to last filled cell
        if (id > pin.length) {
            id = pin.length
        }
        // set cursor position
        this.setState({
            selection: id,
        })
        this.input.focus()
    }

    cycleBlinkAnimation = () => {
        Animated.sequence([
            Animated.timing(this.state.blinkAnim, {
                toValue: 0,
                duration: 1000
            }),
            Animated.timing(this.state.blinkAnim, {
                toValue: 1,
                duration: 50
            }),
            Animated.timing(this.state.blinkAnim, {
                toValue: 1,
                duration: 300
            })
        ]).start((event) => {
            if (event.finished && this.state.isFocused) {
                this.cycleBlinkAnimation()
            }
        })
    }

    onFocus = (event) => {
        console.log('onFocus')
        let {selection} = this.state
        // update cursor position only if it wasn't setted up
        if (selection === undefined) {
            selection = 0
        }

        this.setState({
            selection,
            isFocused: true,
        })
    }

    onBlur = (event) => {
        console.log('onBlur')
        this.setState({
            selection: undefined,
            isFocused: false,
            isLastCharUpdated: false,
        })
    }

    componentWillUpdate(nextProps, nextState) {
        if (this.state.isFocused && !nextState.isFocused) {
            this.state.blinkAnim.stopAnimation()
        }
        //restart animation on focus or when cursor moved
        if ((this.state.selection !== nextState.selection || !this.state.isFocused) && nextState.isFocused) {
            this.state.blinkAnim.stopAnimation(() => {
                this.state.blinkAnim.setValue(1)
                this.cycleBlinkAnimation()
            })
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.isFocused
            && this.state.pin.length === this.props.maxChars  // input is full
            && this.state.selection+1 === this.props.maxChars // cursor is in last cell
            // && prevState.pin[3] !== this.state.pin[3]) { // last cell was changed
            && this.state.isLastCharUpdated) {
            console.log('blur componentDidUpdate')
            this.input.blur()
            setTimeout(this.onPinEntered, 1) // dirty hack, on ios sync call onPinEntered prevents blur
        }
    }

    componentWillMount() {
        this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
    }

    componentDidMount() {
        this.cycleBlinkAnimation()
    }

    componentWillUnmount() {
        this.keyboardDidHideListener.remove()
    }

    keyboardDidHide = () => {
        // see reason in top
        // to prevent unfocussing in IOS simulator with connected hardware keyboard uncomment the following line
        //  or disconnect hardware keyboard by unchecking simulator > Hardware > Keyboard > Connect Hardware Keyboard
        // if (Platform.OS === 'ios') return;
        if (this.state.isFocused) {
            this.input.blur()
        }
    }

    onChangeText = (text) => {
        let {pin, selection} = this.state
        text = text.replace(empty_character, '') //remove first occurrence of empty_character
        let str_replaceAt = function (string, index, replacement) {
            return string.substr(0, index) + replacement + string.substr(index + 1);
        }
        let isLastCharUpdated = false
        if (text.length === 0) { //backspace
            pin = str_replaceAt(pin, selection, '')
            selection -= 1
            selection = Math.max(selection, 0)
        } else { //character entered
            pin = str_replaceAt(pin, selection, text)
            selection += 1
            if (selection >= this.props.maxChars) {
                isLastCharUpdated = true
            }
            selection = Math.min(selection, this.props.maxChars - 1)
        }
        this.setState({pin, selection, isLastCharUpdated})
    }

    onPinEntered = () => {
        if ('function' === typeof this.props.onPinEntered) {
            this.props.onPinEntered(this.state.pin)
        }
    }

    render_cell(id, value, is_active) {
        let style = (is_active) ? [styles.cell, styles.active_cell] : styles.cell
        let animation_style =
            [styles.cursor, {
                opacity: this.state.blinkAnim,         // Bind opacity to animated value
            }]
        return (
            <TouchableWithoutFeedback key={id} onPress={(event) => this.onPressCell(event, id)}>
                <View>
                    <Text style={style}>{value}</Text>
                    { is_active && <Animated.View style={animation_style} />}
                </View>
            </TouchableWithoutFeedback>
        )
    }

    render() {
        let inputs = []
        let {pin, selection} = this.state

        //render cells
        for (let i = 0; i < this.props.maxChars; i++) {
            inputs.push(this.render_cell(i, pin[i], (selection === i)))
        }

        // place cursor after empty_character
        let input_selection = undefined
        // as described in top: Some times during reloads, selection is setting before defaultValue ...
        if (this.input !== undefined && this.state.isFocused) {
            // so set selection after first character (empty_character) only when input is focused
            input_selection = {start: 1, end: 1}
        }

        let root_style = [this.props.style || {}, styles.root]

        return (
            <View style={root_style}>
                <TextInput
                           style={styles.actual_input}
                           ref={(input) => this.input = input}
                           maxLength={2}
                           selection={input_selection}
                           onChangeText={this.onChangeText}
                           autoComplete={false}
                           autoCorrect={false}
                           defaultValue={empty_character}
                           value={empty_character}
                           onFocus={this.onFocus}
                           onBlur={this.onBlur}
                           onSubmitEditing={this.onPinEntered}
                           {...this.props.inputProps}
                />
                <View style={styles.cells_wrapper}>
                    {inputs}
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    root: {},
    actual_input: {
        position: 'absolute',
        display: 'none',
    },
    cells_wrapper: {
        flexDirection: 'row',
    },
    cell: {
        height: ui_size(24.8),
        width: ui_size(19.2),
        textAlign: 'center',
        elevation: 10,
        fontSize: ui_size(18),
        lineHeight: ui_size(24),
        backgroundColor: 'white',
        color: 'black',
        fontWeight: 'bold',
        paddingHorizontal: ui_size(3),
        margin: ui_size(6.7) / 2,
        borderRadius: ui_size(2.5),
        borderBottomColor: 'transparent',
        zIndex: 1,
        overflow: 'hidden', // crop corners in IOS
    },
    active_cell: {
        color: 'rgba(0,0,0,0.4)',
    },
    cursor: {
        position: 'absolute',
        height: 1,
        width: '50%',
        left: '25%',
        bottom: 12,
        borderBottomColor: 'rgba(0,0,0,0.5)',
        borderBottomWidth: 1,
        zIndex: 2,
        elevation: 10,
    }
});