Come far scorrere automaticamente la finestra fuori da dietro la tastiera quando TextInput è attivo?


90

Ho visto questo trucco per app native per scorrere automaticamente la finestra, ma mi chiedo il modo migliore per farlo in React Native ... Quando un <TextInput>campo viene messo a fuoco ed è posizionato in basso nella vista, la tastiera coprirà il campo di testo.

Puoi vedere questo problema nella visualizzazione di esempio di UIExplorer TextInputExample.js.

Qualcuno ha una buona soluzione?


3
Suggerirei di aggiungerlo come problema sul tracker Github e vedere se ne viene fuori qualcosa, poiché questo sarà un reclamo molto comune.
Colin Ramsay

Risposte:


83

Risposta del 2017

Il KeyboardAvoidingViewè probabilmente il modo migliore per andare. Dai un'occhiata ai documenti qui . È davvero semplice rispetto al Keyboardmodulo che offre allo sviluppatore un maggiore controllo per eseguire le animazioni. Spencer Carli ha dimostrato tutti i modi possibili sul suo blog medio .

2015 Risposta

Il modo corretto per farlo react-nativenon richiede librerie esterne, sfrutta il codice nativo e include animazioni.

Definisci prima una funzione che gestirà l' onFocusevento per ogni TextInput(o qualsiasi altro componente a cui desideri scorrere):

// Scroll a component into view. Just pass the component ref string.
inputFocused (refName) {
  setTimeout(() => {
    let scrollResponder = this.refs.scrollView.getScrollResponder();
    scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
      React.findNodeHandle(this.refs[refName]),
      110, //additionalOffset
      true
    );
  }, 50);
}

Quindi, nella tua funzione di rendering:

render () {
  return (
    <ScrollView ref='scrollView'>
        <TextInput ref='username' 
                   onFocus={this.inputFocused.bind(this, 'username')}
    </ScrollView>
  )
}

Questo utilizza l'estensione RCTDeviceEventEmitter eventi della tastiera e il dimensionamento, misura la posizione del componente utilizzando RCTUIManager.measureLayoute calcola l'esatto movimento di scorrimento richiesto in scrollResponderInputMeasureAndScrollToKeyboard.

Potresti voler giocare con il additionalOffsetparametro, per adattarlo alle esigenze del tuo design specifico dell'interfaccia utente.


5
Questa è una bella scoperta, ma per me non è stato sufficiente, perché mentre ScrollView si assicurerà che TextInput sia sullo schermo, ScrollView mostrava ancora contenuti sotto la tastiera che l'utente non poteva scorrere. L'impostazione della proprietà ScrollView "keyboardDismissMode = on-drag" consente all'utente di chiudere la tastiera, ma se non c'è abbastanza contenuto di scorrimento sotto la tastiera, l'esperienza è un po 'stridente. Se ScrollView deve scorrere solo a causa della tastiera in primo luogo e disabiliti il ​​rimbalzo, sembra che non ci sia modo di chiudere la tastiera e mostrare il contenuto di seguito
miracle2k

2
@ miracle2k - Ho una funzione che ripristina la posizione della visualizzazione di scorrimento quando un input è sfocato, cioè quando la tastiera si chiude. Forse questo potrebbe aiutare nel tuo caso?
Sherlock

2
@Sherlock Che aspetto ha la funzione di ripristino della vista con scorrimento sfocato? Soluzione fantastica, comunque :)
Ryan McDermott

8
Nelle versioni più recenti di React Native dovrai chiamare: * import ReactNative da 'react-native'; * prima di chiamare * ReactNative.findNodeHandle () * Altrimenti l'app andrà in crash
amirfl

6
Ora import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129

26

Facebook open source KeyboardAvoidingView in React Native 0.29 per risolvere questo problema. Documentazione ed esempi di utilizzo possono essere trovati qui .


32
Attenzione a KeyboardAvoidingView, semplicemente non è facile da usare. Non si comporta sempre come ti aspetti che dovrebbe. La documentazione è praticamente inesistente.
Renato Back

doc e comportamento stanno migliorando ora
antoine129

Il problema che ho è che KeyboardAvoidingView misura l'altezza della tastiera come 65 sul mio simulatore di iPhone 6 e quindi la mia vista è ancora nascosta dietro la tastiera.
Marc

L'unico modo in cui ho potuto allenare è stato attraverso un approccio di aggiunta di fondi innescato daDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc

12

Abbiamo combinato parte del modulo di codice react-native-keyboard-spacer e il codice di @Sherlock per creare un componente KeyboardHandler che può essere avvolto attorno a qualsiasi vista con elementi TextInput. Funziona come un fascino! :-)

/**
 * Handle resizing enclosed View and scrolling to input
 * Usage:
 *    <KeyboardHandler ref='kh' offset={50}>
 *      <View>
 *        ...
 *        <TextInput ref='username'
 *          onFocus={()=>this.refs.kh.inputFocused(this,'username')}/>
 *        ...
 *      </View>
 *    </KeyboardHandler>
 * 
 *  offset is optional and defaults to 34
 *  Any other specified props will be passed on to ScrollView
 */
'use strict';

var React=require('react-native');
var {
  ScrollView,
  View,
  DeviceEventEmitter,
}=React;


var myprops={ 
  offset:34,
}
var KeyboardHandler=React.createClass({
  propTypes:{
    offset: React.PropTypes.number,
  },
  getDefaultProps(){
    return myprops;
  },
  getInitialState(){
    DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
      if (!frames.endCoordinates) return;
      this.setState({keyboardSpace: frames.endCoordinates.height});
    });
    DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
      this.setState({keyboardSpace:0});
    });

    this.scrollviewProps={
      automaticallyAdjustContentInsets:true,
      scrollEventThrottle:200,
    };
    // pass on any props we don't own to ScrollView
    Object.keys(this.props).filter((n)=>{return n!='children'})
    .forEach((e)=>{if(!myprops[e])this.scrollviewProps[e]=this.props[e]});

    return {
      keyboardSpace:0,
    };
  },
  render(){
    return (
      <ScrollView ref='scrollView' {...this.scrollviewProps}>
        {this.props.children}
        <View style={{height:this.state.keyboardSpace}}></View>
      </ScrollView>
    );
  },
  inputFocused(_this,refName){
    setTimeout(()=>{
      let scrollResponder=this.refs.scrollView.getScrollResponder();
      scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
        React.findNodeHandle(_this.refs[refName]),
        this.props.offset, //additionalOffset
        true
      );
    }, 50);
  }
}) // KeyboardHandler

module.exports=KeyboardHandler;

Qualcosa di facile / ovvio che impedirebbe la visualizzazione della tastiera in un simulatore iOS?
seigel

1
Hai provato Comando + K (Hardware-> Tastiera-> Toggle Software Keboard)?
John kendall,

Prova la versione modificata di questo qui: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Credo che sia meglio perché non si basa su timeout che penso sia fragile imo.
CoderDave

10

Per prima cosa devi installare react-native-keyboardevents .

  1. In XCode, nel navigatore del progetto, fai clic con il pulsante destro del mouse su Librerie ➜ Aggiungi file a [nome del progetto] Vai a node_modules ➜ react-native-keyboardevents e aggiungi il file .xcodeproj
  2. In XCode, nel navigatore del progetto, seleziona il tuo progetto. Aggiungi la lib * .a dal progetto keyboardevents alle fasi di compilazione del progetto ➜ Collega file binario con librerie Fai clic sul file .xcodeproj che hai aggiunto in precedenza nel navigatore del progetto e vai alla scheda Impostazioni di creazione. Assicurati che "Tutto" sia attivato (invece di "Base"). Cerca Percorsi di ricerca intestazione e assicurati che contenga sia $ (SRCROOT) /../ react-native / React e $ (SRCROOT) /../../ React - contrassegna entrambi come ricorsivi.
  3. Esegui il tuo progetto (Cmd + R)

Quindi di nuovo in javascript land:

È necessario importare gli eventi della tastiera nativa reattiva.

var KeyboardEvents = require('react-native-keyboardevents');
var KeyboardEventEmitter = KeyboardEvents.Emitter;

Quindi, nella tua vista, aggiungi un po 'di stato per lo spazio della tastiera e aggiorna dall'ascolto degli eventi della tastiera.

  getInitialState: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, (frames) => {
      this.setState({keyboardSpace: frames.end.height});
    });
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, (frames) => {
      this.setState({keyboardSpace: 0});
    });

    return {
      keyboardSpace: 0,
    };
  },

Infine, aggiungi uno spaziatore alla tua funzione di rendering sotto ogni cosa in modo che quando aumenta le dimensioni aumenti le tue cose.

<View style={{height: this.state.keyboardSpace}}></View>

È anche possibile utilizzare l'API di animazione, ma per semplicità ci limitiamo ad aggiustare dopo l'animazione.


1
Sarebbe fantastico vedere un esempio di codice / qualche informazione in più su come eseguire l'animazione. Il salto è piuttosto stravagante, e lavorando solo con i metodi "mostra" e "mostrava", non riesco a capire come indovinare quanto sarà lunga l'animazione della tastiera o quanto è alta da "mostrerà".
Stephen

2
react-native@0.11.0-rc ora invia gli eventi della tastiera (ad esempio, "keyboardWillShow") tramite DeviceEventEmitter, in modo da poter registrare i listener per questi eventi. Quando si ha a che fare con un ListView, tuttavia, ho scoperto che la chiamata a scrollTo () sulla scrollview di ListView ha funzionato meglio: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); viene chiamata su TextInput di una riga quando riceve un evento onFocus.
Jed Lau

4
Questa risposta non è più valida, poiché RCTDeviceEventEmitter fa il lavoro.
Sherlock


6

Prova questo:

import React, {
  DeviceEventEmitter,
  Dimensions
} from 'react-native';

...

getInitialState: function() {
  return {
    visibleHeight: Dimensions.get('window').height
  }
},

...

componentDidMount: function() {
  let self = this;

  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    self.keyboardWillShow(e);
  });

  DeviceEventEmitter.addListener('keyboardWillHide', function(e: Event) {
      self.keyboardWillHide(e);
  });
}

...

keyboardWillShow (e) {
  let newSize = Dimensions.get('window').height - e.endCoordinates.height;
  this.setState({visibleHeight: newSize});
},

keyboardWillHide (e) {
  this.setState({visibleHeight: Dimensions.get('window').height});
},

...

render: function() {
  return (<View style={{height: this.state.visibleHeight}}>your view code here...</View>);
}

...

Ha funzionato per me. La vista fondamentalmente si restringe quando viene visualizzata la tastiera e cresce di nuovo quando è nascosta.


Inoltre, questa soluzione funziona bene (RN 0.21.0) stackoverflow.com/a/35874233/3346628
Pomo

usa this.keyboardWillHide.bind (this) invece di self
animekun

Usa la tastiera invece DeviceEventEmitter
Madura Pradeep


4

Forse è troppo tardi, ma la soluzione migliore è utilizzare una libreria nativa, troppo IQKeyboardManager

Basta trascinare e rilasciare la directory IQKeyboardManager dal progetto demo al progetto iOS. Questo è tutto. Inoltre è possibile impostare alcuni valori, poiché è abilitata la barra degli strumenti o lo spazio tra l'immissione di testo e la tastiera nel file AppDelegate.m. Maggiori dettagli sulla personalizzazione si trovano nel collegamento alla pagina GitHub che ho aggiunto.


1
Questa è un'opzione eccellente. Vedi anche github.com/douglasjunior/react-native-keyboard-manager per una versione inclusa per ReactNative: è facile da installare.
Loevborg

3

Ho usato TextInput.onFocus e ScrollView.scrollTo.

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},

2

@Stefano

Se non ti dispiace non avere l'altezza animata esattamente alla stessa velocità con cui appare la tastiera, puoi semplicemente usare LayoutAnimation, in modo che almeno l'altezza non salti al suo posto. per esempio

importa LayoutAnimation da react-native e aggiungi i seguenti metodi al tuo componente.

getInitialState: function() {
    return {keyboardSpace: 0};
  },
   updateKeyboardSpace: function(frames) {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: frames.end.height});
  },

  resetKeyboardSpace: function() {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: 0});
  },

  componentDidMount: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

  componentWillUnmount: function() {
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

Alcune animazioni di esempio sono (sto usando quella primaverile sopra):

var animations = {
  layout: {
    spring: {
      duration: 400,
      create: {
        duration: 300,
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity,
      },
      update: {
        type: LayoutAnimation.Types.spring,
        springDamping: 400,
      },
    },
    easeInEaseOut: {
      duration: 400,
      create: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.scaleXY,
      },
      update: {
        type: LayoutAnimation.Types.easeInEaseOut,
      },
    },
  },
};

AGGIORNARE:

Vedi la risposta di @ sherlock di seguito, a partire da react-native 0.11 il ridimensionamento della tastiera può essere risolto utilizzando la funzionalità integrata.


2

Puoi combinare alcuni metodi in qualcosa di un po 'più semplice.

Collega un listener onFocus ai tuoi input

<TextInput ref="password" secureTextEntry={true} 
           onFocus={this.scrolldown.bind(this,'password')}
/>

Il nostro metodo di scorrimento verso il basso è simile a:

scrolldown(ref) {
    const self = this;
    this.refs[ref].measure((ox, oy, width, height, px, py) => {
        self.refs.scrollView.scrollTo({y: oy - 200});
    });
}

Questo dice alla nostra vista a scorrimento (ricordati di aggiungere un riferimento) di scorrere fino alla posizione del nostro input focalizzato - 200 (è più o meno la dimensione della tastiera)

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

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

keyboardDidHide(e) {
    this.refs.scrollView.scrollTo({y: 0});
}

Qui ripristiniamo la nostra visualizzazione a scorrimento all'inizio,

inserisci qui la descrizione dell'immagine


2
@ potresti fornire il tuo metodo render ()?
valerybodak

0

Sto usando un metodo più semplice, ma non è ancora animato. Ho uno stato del componente chiamato "bumpedUp" che di default è 0, ma impostato a 1 quando textInput diventa attivo, in questo modo:

Sul mio testo Input:

onFocus={() => this.setState({bumpedUp: 1})}
onEndEditing={() => this.setState({bumpedUp: 0})}

Ho anche uno stile che conferisce al contenitore di avvolgimento di tutto su quello schermo un margine inferiore e un margine superiore negativo, come questo:

mythingscontainer: {
  flex: 1,
  justifyContent: "center",
  alignItems: "center",
  flexDirection: "column",
},
bumpedcontainer: {
  marginBottom: 210,
  marginTop: -210,
},

E poi sul contenitore di avvolgimento, ho impostato gli stili in questo modo:

<View style={[styles.mythingscontainer, this.state.bumpedUp && styles.bumpedcontainer]}>

Quindi, quando lo stato "bumpedUp" viene impostato su 1, lo stile bumpedcontainer si attiva e sposta il contenuto in alto.

Un po 'hacky ei margini sono hardcoded, ma funziona :)


0

Uso la risposta brysgo per sollevare la parte inferiore della mia visualizzazione a scorrimento. Quindi uso onScroll per aggiornare la posizione corrente di scrollview. Ho quindi trovato questo React Native: Ottenere la posizione di un elemento per ottenere la posizione dell'input di testo. Quindi eseguo alcuni semplici calcoli per capire se l'input è nella visualizzazione corrente. Quindi uso scrollTo per spostare l'importo minimo più un margine. È abbastanza liscio. Ecco il codice per la parte a scorrimento:

            focusOn: function(target) {
                return () => {
                    var handle = React.findNodeHandle(this.refs[target]);
                    UIManager.measureLayoutRelativeToParent( handle, 
                        (e) => {console.error(e)}, 
                        (x,y,w,h) => {
                            var offs = this.scrollPosition + 250;
                            var subHeaderHeight = (Sizes.width > 320) ? Sizes.height * 0.067 : Sizes.height * 0.077;
                            var headerHeight = Sizes.height / 9;
                            var largeSpace = (Sizes.height - (subHeaderHeight + headerHeight));
                            var shortSpace = largeSpace - this.keyboardOffset;
                            if(y+h >= this.scrollPosition + shortSpace) {
                                this.refs.sv.scrollTo(y+h - shortSpace + 20);
                            }
                            if(y < this.scrollPosition) this.refs.sv.scrollTo(this.scrollPosition - (this.scrollPosition-y) - 20 );
                        }
                     );
                };
            },

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.