Jak automatycznie wysunąć okno zza klawiatury, gdy fokus ma TextInput?

90

Widziałem ten sposób na automatyczne przewijanie okna przez aplikacje natywne, ale zastanawiam się, jak to zrobić w React Native ... Kiedy <TextInput>pole zostanie wyróżnione i zostanie umieszczone nisko w widoku, klawiatura zakryje pole tekstowe.

Możesz zobaczyć ten problem na przykładzie TextInputExample.jswidoku UIExplorer .

Czy ktoś ma dobre rozwiązanie?

McG
źródło
3
Sugerowałbym dodanie tego jako problemu do modułu śledzącego Github i sprawdzenie, czy coś z tego wyniknie, ponieważ będzie to bardzo częsta skarga.
Colin Ramsay,

Odpowiedzi:

83

Odpowiedź z 2017 r

To KeyboardAvoidingViewprawdopodobnie najlepszy sposób, aby teraz iść. Sprawdź dokumenty tutaj . Jest to naprawdę proste w porównaniu z Keyboardmodułem, który daje programistom większą kontrolę nad wykonywaniem animacji. Spencer Carli zademonstrował wszystkie możliwe sposoby na swoim blogu .

Odpowiedź 2015

Prawidłowy sposób wykonania tego w programie react-nativenie wymaga zewnętrznych bibliotek, wykorzystuje kod natywny i zawiera animacje.

Najpierw zdefiniuj funkcję, która będzie obsługiwać onFocuszdarzenie dla każdego TextInput(lub dowolnego innego komponentu, do którego chcesz przewinąć):

// 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);
}

Następnie w funkcji renderującej:

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

Używa RCTDeviceEventEmitterdo zdarzeń klawiatury i zmiany rozmiaru, mierzy położenie komponentu przy użyciu RCTUIManager.measureLayouti oblicza dokładny ruch przewijania wymagany w scrollResponderInputMeasureAndScrollToKeyboard.

Możesz bawić się tym additionalOffsetparametrem, aby dopasować go do potrzeb konkretnego projektu interfejsu użytkownika.

Sherlock
źródło
5
To fajne znalezisko, ale dla mnie to nie wystarczyło, ponieważ podczas gdy ScrollView upewni się, że TextInput jest na ekranie, ScrollView nadal wyświetlał zawartość poniżej klawiatury, do której użytkownik nie mógł przewinąć. Ustawienie właściwości ScrollView „keyboardDismissMode = on-drag” umożliwia użytkownikowi zamknięcie klawiatury, ale jeśli pod klawiaturą nie ma wystarczającej ilości przewijania, działanie jest nieco irytujące. Jeśli ScrollView musi przewijać się tylko z powodu klawiatury w pierwszej kolejności, a wyłączysz odbijanie, wydaje się, że nie ma sposobu, aby odrzucić klawiaturę i pokazać zawartość poniżej
cud2k
2
@ miracle2k - Mam funkcję, która resetuje pozycję widoku przewijania, gdy wejście jest zamazane, tj. gdy zamyka się klawiatura. Może to pomoże w Twoim przypadku?
Sherlock,
2
@Sherlock Jak wygląda funkcja resetowania widoku przewijania rozmycia? Swoją drogą super rozwiązanie :)
Ryan McDermott
8
W nowszych wersjach React Native będziesz musiał wywołać: * import ReactNative z'reak-native '; * przed wywołaniem * ReactNative.findNodeHandle () * W przeciwnym razie aplikacja ulegnie awarii
amirfl
6
Teraz import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/ ...
antoine129
26

Facebook open source KeyboardAvoidingView reaguje na natywne 0.29, aby rozwiązać ten problem. Dokumentację i przykład użycia można znaleźć tutaj .

farwayer
źródło
32
Uważaj na KeyboardAvoidingView, po prostu nie jest łatwy w użyciu. Nie zawsze zachowuje się tak, jak się tego spodziewasz. Dokumentacja praktycznie nie istnieje.
Renato Powrót
doc i zachowanie są teraz coraz lepsze
antoine129
Problem, który mam, polega na tym, że KeyboardAvoidingView mierzy wysokość klawiatury jako 65 na moim symulatorze iPhone'a 6, więc mój widok jest nadal ukryty za klawiaturą.
Marc
Jedynym sposobem, w jaki mogłem nim zarządzać, było podejście typu bottompadding uruchomione przezDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc
12

Połączyliśmy część kodu w postaci reaguj-natywna-klawiatura-odstępnik i kod z @Sherlock, aby utworzyć komponent KeyboardHandler, który można zawinąć wokół dowolnego widoku z elementami TextInput. Działa jak marzenie! :-)

/**
 * 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;
John Kendall
źródło
Czy jest coś łatwego / oczywistego, co uniemożliwiłoby wyświetlanie klawiatury w symulatorze iOS?
seigel
1
Czy próbowałeś Command + K (Sprzęt-> Klawiatura-> Przełącz oprogramowanie Keboard)?
John Kendall
Wypróbuj zmodyfikowaną wersję tego tutaj: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Uważam, że jest to lepsze, ponieważ nie polega na żadnych limitach czasu, które moim zdaniem są delikatne.
CoderDave
10

Najpierw musisz zainstalować zdarzeniareak-native-keyboardevents .

  1. W XCode, w nawigatorze projektów, kliknij prawym przyciskiem Biblioteki ➜ Dodaj pliki do [nazwa twojego projektu] Idź do node_modules ➜ reaguj-native-keyboardevents i dodaj plik .xcodeproj
  2. W XCode, w nawigatorze projektów, wybierz projekt. Dodaj bibliotekę lib * .a z projektu keyboardevents do faz budowania projektu ➜ Połącz plik binarny z bibliotekami Kliknij dodany wcześniej plik .xcodeproj w nawigatorze projektu i przejdź do zakładki Ustawienia kompilacji. Upewnij się, że opcja „Wszystkie” jest włączona (zamiast „Podstawowe”). Poszukaj ścieżek wyszukiwania nagłówków i upewnij się, że zawiera zarówno $ (SRCROOT) /../ react-native / React i $ (SRCROOT) /../../ React - zaznacz oba jako rekurencyjne.
  3. Uruchom swój projekt (Cmd + R)

Następnie z powrotem w krainie javascript:

Musisz zaimportować zdarzeniareak-native-keyboardevents.

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

Następnie w swoim widoku dodaj stan dla przestrzeni klawiatury i zaktualizuj nasłuchiwanie zdarzeń klawiatury.

  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,
    };
  },

Na koniec dodaj element dystansowy do funkcji renderowania pod wszystkim, aby gdy zwiększył rozmiar, podniósł swoje rzeczy.

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

Możliwe jest również użycie animacji API, ale dla uproszczenia dostosowujemy się po animacji.

brysgo
źródło
1
Byłoby wspaniale zobaczyć przykładowy kod / więcej informacji o tym, jak wykonać animację. Skok jest dość szarpany, a pracując tylko z metodami „pokaże” i „pokaże”, nie mogę do końca zgadnąć, jak długa będzie animacja klawiatury lub jak wysoka będzie od „pokaże się”.
Stephen
2
[email protected] wysyła teraz zdarzenia klawiatury (np. „keyboardWillShow”) przez DeviceEventEmitter, więc możesz zarejestrować nasłuchiwania dla tych zdarzeń. Jednak podczas pracy z ListView stwierdziłem, że wywołanie scrollTo () w widoku scrollview ListView działało lepiej: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); jest wywoływane w wierszu TextInput, gdy odbiera zdarzenie onFocus.
Jed Lau
4
Ta odpowiedź nie jest już prawidłowa, ponieważ RCTDeviceEventEmitter wykonuje zadanie.
Sherlock,
6

Spróbuj tego:

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>);
}

...

U mnie to zadziałało. Widok zasadniczo kurczy się, gdy klawiatura jest wyświetlana, i odrasta, gdy jest ukryta.

pomo
źródło
Również to rozwiązanie działa dobrze (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo
użyj this.keyboardWillHide.bind (this) zamiast self
animekun
Zamiast tego użyj klawiatury DeviceEventEmitter
Madura Pradeep
4

Może jest za późno, ale najlepszym rozwiązaniem jest użycie natywnej biblioteki IQKeyboardManager

Po prostu przeciągnij i upuść katalog IQKeyboardManager z projektu demonstracyjnego do projektu iOS. Otóż ​​to. Możesz także skonfigurować niektóre wartości, takie jak włączony pasek narzędzi, lub odstęp między wprowadzaniem tekstu a klawiaturą w pliku AppDelegate.m. Więcej szczegółów na temat dostosowywania znajduje się w linku do strony GitHub, który dodałem.

Adrian Zghibarta
źródło
1
To doskonała opcja. Zobacz także github.com/douglasjunior/react-native-keyboard-manager, aby uzyskać wersję zapakowaną dla ReactNative - jest łatwa do zainstalowania.
loevborg
3

Użyłem TextInput.onFocus i ScrollView.scrollTo.

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},
shohey1226
źródło
2

@Stephen

Jeśli nie masz nic przeciwko temu, by wysokość animacji była dokładnie taka sama, jak pojawia się klawiatura, możesz po prostu użyć LayoutAnimation, aby przynajmniej wysokość nie wskoczyła na miejsce. na przykład

zaimportuj LayoutAnimation z React-Native i dodaj następujące metody do swojego komponentu.

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);
  },

Oto kilka przykładowych animacji (używam wiosennej powyżej):

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,
      },
    },
  },
};

AKTUALIZACJA:

Zobacz odpowiedź @ sherlock poniżej, ponieważ odreak-native 0.11 zmiana rozmiaru klawiatury może być rozwiązana za pomocą wbudowanych funkcji.

deanmcpherson
źródło
2

Możesz połączyć kilka metod w coś prostszego.

Dołącz odbiornik onFocus do swoich danych wejściowych

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

Nasza metoda przewijania w dół wygląda mniej więcej tak:

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

To mówi naszemu widokowi przewijania (pamiętaj, aby dodać ref), aby przewinął w dół do pozycji naszego skoncentrowanego wejścia - 200 (to mniej więcej rozmiar klawiatury)

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

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

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

Tutaj resetujemy nasz widok przewijania z powrotem do góry,

wprowadź opis obrazu tutaj

Nath
źródło
2
@ czy mógłbyś podać swoją metodę render ()?
valerybodak
0

Używam prostszej metody, ale nie jest jeszcze animowana. Mam stan komponentu o nazwie „bumpedUp”, który domyślnie wynosi 0, ale jest ustawiony na 1, gdy fokus zostanie ustawiony na textInput, na przykład:

W moim tekście

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

Mam też styl, który nadaje opakowaniom wszystkiego na tym ekranie dolny i ujemny górny margines, na przykład:

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

Następnie na opakowaniu do pakowania ustawiam style w ten sposób:

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

Tak więc, gdy stan „bumpedUp” zostanie ustawiony na 1, styl bumpedcontainer włącza się i przesuwa zawartość w górę.

Trochę hacky i marginesy są zakodowane na stałe, ale działa :)

Stirman
źródło
0

Używam odpowiedzi brysgo, aby podnieść dolną część widoku przewijania. Następnie używam onScroll, aby zaktualizować bieżącą pozycję widoku scrollview. Następnie znalazłem ten React Native: Pobieranie pozycji elementu, aby uzyskać pozycję wejścia tekstowego. Następnie wykonuję prostą matematykę, aby dowiedzieć się, czy dane wejściowe są w bieżącym widoku. Następnie używam scrollTo, aby przesunąć minimalną kwotę plus margines. Jest całkiem gładki. Oto kod części przewijanej:

            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 );
                        }
                     );
                };
            },
aintnorest
źródło
0

Ja też odpowiadam na to pytanie. Wreszcie rozwiązuję to, definiując wysokość każdej sceny, na przykład:

<Navigator
    ...
    sceneStyle={{height: **}}
/>

Używam również modułu innej firmy https://github.com/jaysoo/react-native-extra-dimensions-android, aby uzyskać rzeczywistą wysokość.

Renguang Dong
źródło