Как автоматически выдвинуть окно из-за клавиатуры, когда TextInput имеет фокус?

90

Я видел этот прием для нативных приложений для автоматической прокрутки окна, но мне интересно, как это сделать в React Native ... Когда <TextInput>поле получает фокус и располагается низко в представлении, клавиатура закрывает текстовое поле.

Вы можете увидеть эту проблему в примере представления UIExplorer TextInputExample.js.

Есть у кого-нибудь хорошее решение?

McG
источник
3
Я бы предложил добавить это как проблему в трекер Github и посмотреть, выйдет ли из этого что-нибудь, поскольку это будет очень распространенная жалоба.
Колин Рамзи

Ответы:

83

Ответ 2017

KeyboardAvoidingView, Вероятно, лучший способ идти. Ознакомьтесь с документацией здесь . Это действительно просто по сравнению с Keyboardмодулем, который дает разработчику больше возможностей для управления анимацией. Спенсер Карли продемонстрировал все возможные способы в своем блоге .

2015 Ответ

Правильный способ сделать это react-nativeне требует внешних библиотек, использует собственный код и включает анимацию.

Сначала определите функцию, которая будет обрабатывать onFocusсобытие для каждого TextInput(или любого другого компонента, к которому вы хотите перейти):

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

Затем в вашей функции рендеринга:

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

При этом используются RCTDeviceEventEmitterсобытия клавиатуры и размер, измеряется позиция используемого компонента RCTUIManager.measureLayoutи вычисляется точное движение прокрутки, необходимое в scrollResponderInputMeasureAndScrollToKeyboard.

Вы можете поэкспериментировать с additionalOffsetпараметром, чтобы он соответствовал потребностям вашего конкретного дизайна пользовательского интерфейса.

Шерлок
источник
5
Это хорошая находка, но для меня этого было недостаточно, потому что, хотя ScrollView будет следить за тем, чтобы TextInput был на экране, ScrollView все еще показывал контент под клавиатурой, к которому пользователь не мог прокрутить. Установка свойства ScrollView «keyboardDismissMode = on-drag» позволяет пользователю закрыть клавиатуру, но если под клавиатурой недостаточно прокручиваемого содержимого, работа будет немного неприятной. Если ScrollView должен прокручиваться только из-за клавиатуры в первую очередь, и вы отключите подпрыгивание, то, похоже, нет способа закрыть клавиатуру и показать содержимое ниже
miracle2k
2
@ miracle2k - у меня есть функция, которая сбрасывает положение прокрутки, когда ввод размывается, то есть когда закрывается клавиатура. Может быть, это поможет в вашем случае?
Шерлок,
2
@Sherlock Как выглядит эта функция сброса режима размытия прокрутки? Кстати, отличное решение :)
Райан Макдермотт,
8
В более новых версиях React Native вам нужно будет вызвать: * import ReactNative из 'react-native'; * перед вызовом * ReactNative.findNodeHandle () * В противном случае приложение выйдет из
строя
6
Теперь import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129
26

Чтобы решить эту проблему, Facebook открыл исходный код KeyboardAvoidingView в react native 0.29. Документацию и пример использования можно найти здесь .

дальнобойщик
источник
32
Остерегайтесь KeyboardAvoidingView, его просто нелегко использовать. Он не всегда ведет себя так, как вы ожидаете. Документация практически отсутствует.
Renato Back
Док и поведение сейчас улучшаются
antoine129
У меня проблема в том, что KeyboardAvoidingView измеряет высоту клавиатуры как 65 на моем симуляторе iPhone 6, поэтому мой взгляд все еще скрыт за клавиатурой.
Marc
Единственный способ, которым я мог управлять этим, заключался в подходе с нижним отступом, инициированномDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc
12

Мы объединили часть кода формы react-native-keyboard-spacer и код из @Sherlock, чтобы создать компонент KeyboardHandler, который можно обернуть вокруг любого View с элементами TextInput. Работает как шарм! :-)

/**
 * 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;
Джон Кендалл
источник
Что-нибудь простое / очевидное, что не позволяет отображать клавиатуру в симуляторе iOS?
seigel
1
Вы пробовали Command + K (Оборудование-> Клавиатура-> Переключить программную клавиатуру)?
Джон Кендалл
Попробуйте модифицированную версию этого здесь: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Я считаю, что это лучше, потому что он не полагается на какие-либо тайм-ауты, которые я считаю хрупкими.
CoderDave 03
10

Сначала вам нужно установить response-native-keyboardevents .

  1. В XCode в навигаторе проекта щелкните правой кнопкой мыши «Библиотеки» ➜ «Добавить файлы в [название вашего проекта]». Перейдите в node_modules ➜ response-native-keyboardevents и добавьте файл .xcodeproj.
  2. В XCode в навигаторе проекта выберите свой проект. Добавьте lib * .a из проекта keyboardevents на этапы сборки вашего проекта ➜ Свяжите двоичный файл с библиотеками. Щелкните файл .xcodeproj, который вы добавили ранее в навигаторе проекта, и перейдите на вкладку «Параметры сборки». Убедитесь, что включено «Все» (вместо «Базовое»). Найдите пути поиска заголовков и убедитесь, что они содержат как $ (SRCROOT) /../ response-native / React, так и $ (SRCROOT) /../../ React - отметьте оба как рекурсивные.
  3. Запустите свой проект (Cmd + R)

Затем вернемся в страну javascript:

Вам необходимо импортировать файл response-native-keyboardevents.

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

Затем, на ваш взгляд, добавьте некоторое состояние для пространства клавиатуры и обновите от прослушивания событий клавиатуры.

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

Наконец, добавьте прокладку к вашей функции рендеринга под всем, чтобы при ее увеличении размер поднимался вверх.

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

Также можно использовать api анимации, но для простоты мы просто настраиваем после анимации.

Brysgo
источник
1
Было бы здорово увидеть пример кода / дополнительную информацию о том, как сделать анимацию. Прыжок довольно резкий, и работая только с методами «покажет» и «действительно показал», я не могу понять, как угадать, какой длины будет анимация клавиатуры или какой высоты она из «покажет».
Стивен
2
react-native@0.11.0-rc теперь отправляет события клавиатуры (например, «keyboardWillShow») через DeviceEventEmitter, поэтому вы можете зарегистрировать слушателей для этих событий. Однако при работе с ListView я обнаружил, что вызов scrollTo () в scrollview ListView работает лучше: он вызывается this.listView.getScrollResponder().scrollTo(rowID * rowHeight); для TextInput строки, когда он получает событие onFocus.
Джед Лау
4
Этот ответ больше недействителен, так как RCTDeviceEventEmitter выполняет свою работу.
Шерлок
6

Попробуй это:

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

...

У меня это сработало. Вид в основном сжимается при отображении клавиатуры и снова увеличивается, когда она скрыта.

помо
источник
Кроме того, это решение хорошо работает (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo
используйте this.keyboardWillHide.bind (this) вместо себя
animekun
Используйте клавиатуру вместо DeviceEventEmitter
Мадура Прадип
4

Просто хотел упомянуть, теперь есть KeyboardAvoidingView в РН есть. Просто импортируйте его и используйте как любой другой модуль в RN.

Вот ссылка на коммит в RN:

https://github.com/facebook/react-native/commit/8b78846a9501ef9c5ce9d1e18ee104bfae76af2e

Доступно с версии 0.29.0

Они также включили пример на UIExplorer.

Аакаш Сигдел
источник
4

Возможно, уже поздно, но лучшее решение - использовать собственную библиотеку IQKeyboardManager.

Просто перетащите каталог IQKeyboardManager из демонстрационного проекта в свой проект iOS. Вот и все. Также вы можете настроить некоторые значения, например isToolbar, или расстояние между вводом текста и клавиатурой в файле AppDelegate.m. Более подробная информация о настройке находится в добавленной мной ссылке на страницу GitHub.

Адриан Згибарта
источник
1
Это отличный вариант. Также см. Github.com/douglasjunior/react-native-keyboard-manager для версии, упакованной для ReactNative - ее легко установить.
loevborg
3

Я использовал TextInput.onFocus и ScrollView.scrollTo.

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},
shohey1226
источник
2

@Стивен

Если вы не против, чтобы высота анимировалась с той же скоростью, что и клавиатура, вы можете просто использовать LayoutAnimation, чтобы по крайней мере высота не встала на место. например

импортируйте LayoutAnimation из react-native и добавьте следующие методы в свой компонент.

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

Вот несколько примеров анимации (я использую весеннюю выше):

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

ОБНОВИТЬ:

См. Ответ @sherlock ниже, начиная с response-native 0.11 изменение размера клавиатуры может быть решено с использованием встроенных функций.

Deanmcpherson
источник
2

Вы можете объединить несколько методов в нечто более простое.

Прикрепите прослушиватель onFocus к своим входам

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

Наш метод прокрутки вниз выглядит примерно так:

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

Это говорит нашему представлению прокрутки (не забудьте добавить ссылку), чтобы прокрутить вниз до позиции нашего сфокусированного ввода - 200 (это примерно размер клавиатуры)

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

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

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

Здесь мы сбрасываем нашу прокрутку обратно наверх,

введите описание изображения здесь

Натх
источник
2
@ не могли бы вы предоставить свой метод render ()?
валерибодак
0

Я использую более простой метод, но он еще не анимирован. У меня есть состояние компонента под названием "bumpedUp", которое по умолчанию равно 0, но устанавливается в 1, когда textInput получает фокус, например:

На моем textInput:

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

У меня также есть стиль, который дает упаковочному контейнеру всего на этом экране нижнее поле и отрицательное верхнее поле, например:

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

А затем в контейнере для упаковки я установил такие стили:

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

Итак, когда состояние «bumpedUp» становится равным 1, срабатывает стиль bumpedcontainer и перемещает контент вверх.

Kinda hacky и поля жестко запрограммированы, но это работает :)

Штирман
источник
0

Я использую brysgo answer, чтобы поднять нижнюю часть прокрутки. Затем я использую onScroll, чтобы обновить текущую позицию прокрутки. Затем я нашел этот React Native: получение позиции элемента для получения позиции текстового ввода. Затем я выполняю простую математику, чтобы выяснить, находится ли ввод в текущем представлении. Затем я использую scrollTo, чтобы переместить минимальную сумму плюс маржа. Это довольно гладко. Вот код для прокручиваемой части:

            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
источник
0

Я тоже встречаю этот вопрос. Наконец, я решаю это, определяя высоту каждой сцены, например:

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

И я также использую сторонний модуль https://github.com/jaysoo/react-native-extra-dimensions-android, чтобы получить реальную высоту.

Рэнгуанг Донг
источник