Як автоматично висунути вікно ззаду клавіатури, коли TextInput має фокус?


90

Я бачив цей хак для власних додатків для автоматичного прокручування вікна, але цікаво, як це зробити найкраще у React Native ... Коли <TextInput>поле фокусується і розташоване низько у поданні, клавіатура закриє текстове поле.

Ви можете побачити цю проблему в прикладі подання UIExplorer TextInputExample.js.

Хтось має гарне рішення?


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 = при перетягуванні" дозволяє користувачеві відхилити клавіатуру, але якщо під клавіатурою недостатньо вмісту прокрутки, досвід трохи заважає. Якщо ScrollView потрібно прокручувати лише в першу чергу через клавіатуру, а ви відключаєте підскакування, тоді, здається, немає можливості відхилити клавіатуру та показати вміст нижче
miracle2k

2
@ miracle2k - у мене є функція, яка скидає позицію перегляду прокрутки, коли вхід розмитий, тобто коли клавіатура закривається. Можливо, це може допомогти у вашому випадку?
Шерлок,

2
@Sherlock Як виглядає ця функція скидання вигляду прокрутки розмиття? До речі, чудове рішення :)
Райан Макдермотт,

8
У нових версіях React Native вам потрібно буде зателефонувати: * імпортувати ReactNative з 'response-native'; * перед викликом * ReactNative.findNodeHandle () * В іншому випадку програма вийде з
ладу

6
Тепер import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851 / ...
antoine129

26

Facebook вирішив цю проблему у відкритому коді KeyboardAvoidingView 0.29 для реагування. Документацію та приклад використання можна знайти тут .


32
Остерігайтеся KeyboardAvoidingView, він просто не простий у використанні. Це не завжди поводиться так, як ви очікуєте. Документація практично відсутня.
Ренато Назад

doc та поведінка зараз покращуються
antoine129

У мене проблема полягає в тому, що KeyboardAvoidingView вимірює висоту клавіатури як 65 на моєму симуляторі iPhone 6, і тому мій погляд все ще прихований за клавіатурою.
Марк

Єдиний спосіб, яким я міг керувати, - це підхід, який було ініційованоDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Марк

12

Ми об’єднали частину форми коду response-native-keyboard-spacer та код із @Sherlock, щоб створити компонент KeyboardHandler, який можна обернути навколо будь-якого подання з елементами 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?
сейгель

1
Ви пробували Command + K (Обладнання-> Клавіатура-> Переключити програмну клавіатуру)?
Джон Кендалл,

Спробуйте змінену версію цього тут: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Я вважаю, що це краще, оскільки воно не покладається на будь-які таймаути, які, на мою думку, є неміцними imo.
CoderDave

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 land:

Вам потрібно імпортувати 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, але для простоти ми просто налаштовуємось після анімації.


1
Було б чудово побачити приклад коду / трохи більше інформації про те, як зробити анімацію. Стрибок досить неприємний, і, працюючи лише з методами "покаже" та "показав", я не можу зрозуміти, як здогадатися, наскільки довгою буде анімація клавіатури або наскільки вона висока від "покаже".
Стівен

2
реагувати-натив@0.11.0-rc тепер надсилає події клавіатури (наприклад, "keyboardWillShow") через DeviceEventEmitter, щоб ви могли зареєструвати слухачів для цих подій. Однак, маючи справу з ListView, я виявив, що виклик scrollTo () у списку прокрутки ListView працював краще: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); це викликається в текстовому введенні рядка, коли він отримує подію onFocus.
Jed Lau,

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
помо

використовувати this.keyboardWillHide.bind (this) замість self
animekun

Використовуйте замість клавіатури DeviceEventEmitter
Мадура Прадіп


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

2

@Stephen

Якщо ви не проти, щоб анімація висоти не була точно такою ж, як і на клавіатурі, ви можете просто скористатися LayoutAnimation, щоб принаймні висота не встала на місце. напр

імпортуйте LayoutAnimation із реакції-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 нижче, станом на 0,11, реагуючи на рідну реакцію, розмір клавіатури можна вирішити за допомогою вбудованої функціональності.


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 ()?
valerybodak

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 вмикається і переміщує вміст вгору.

Напевно хакі і поля чітко закодовані, але це працює :)


0

Я використовую brysgo відповідь, щоб підняти нижню частину свого перегляду прокрутки. Потім я використовую 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 );
                        }
                     );
                };
            },

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.