当TextInput具有焦点时,如何从键盘后面自动滑动窗口?


90

我已经见过针对本机应用程序自动滚动窗口的一种技巧,但我想知道在React Native中实现此目的的最佳方法...当某个<TextInput>字段获得焦点并且在视图中位于较低位置时,键盘将覆盖文本字段。

您可以在示例UIExplorer的TextInputExample.js视图中看到此问题。

有没有人有一个好的解决方案?


3
我建议将此问题添加到Github跟踪器上,并查看是否有问题,因为这将是非常普遍的投诉。
科林·拉姆齐

Answers:


83

2017年答案

KeyboardAvoidingView可能是现在最好的方法。在此处查看文档。与Keyboard提供开发人员更多控制权以执行动画的模块相比,它确实非常简单。斯宾塞·卡利(Spencer Carli)他的中型博客上展示了所有可能的方式。

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参数,以适合特定UI设计的需求。


5
这是一个不错的发现,但对我来说还不够,因为尽管ScrollView可以确保TextInput在屏幕上,但ScrollView仍在用户无法滚动到的键盘下方显示内容。将ScrollView属性设置为“ keyboardDismissMode = on-drag”允许用户关闭键盘,但是如果键盘下方没有足够的滚动内容,则体验会有些刺耳。如果ScrollView首先仅是因为键盘而需要滚动,并且您禁用了弹跳功能,那么似乎无法解散键盘并在下面显示内容
miracle2k

2
@ miracle2k-我有一个功能可以在输入模糊时(即,在键盘关闭时)重置滚动视图的位置。也许这对您有帮助?
Sherlock

2
@Sherlock模糊滚动视图重置功能是什么样的?很棒的解决方案:)
Ryan McDermott 2015年

8
在较新的React Native版本中,您需要调用:*从“ react-native”导入ReactNative;*在调用之前* ReactNative.findNodeHandle()*否则应用程序将崩溃
amirfl '16

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

26

Facebook 在响应本机0.29中开源 KeyboardAvoidingView以解决此问题。文档和用法示例可以在此处找到。


32
提防KeyboardAvoidingView,它并不容易使用。它并不总是像您期望的那样运行。文件几乎不存在。
Renato

文档和行为现在变得越来越好
antoine129'9

我的问题是KeyboardAvoidingView在我的iPhone 6模拟器上将键盘高度测量为65,因此我的视图仍然隐藏在键盘后面。
2016年

我唯一可以管理它的方法是通过底部填充方法触发DeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
马克

12

我们结合了一些代码形式react-native-keyboard-spacer和@Sherlock中的代码,以创建一个KeyboardHandler组件,该组件可以包装在带有TextInput元素的任何View周围。奇迹般有效!:-)

/**
 * 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 2015年

1
您是否尝试过Command + K(硬件->键盘-> Toggle Software Keboard)?
约翰·肯德尔

在此尝试修改后的版本: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3我相信这样做会更好,因为它不依赖任何我认为是易碎的imo超时。
CoderDave

10

首先,您需要安装react-native-keyboardevents

  1. 在XCode中的项目导航器中,右键单击Libraries(库)➜Add Files(添加文件到[您的项目名称])转到node_modules➜react-native-keyboardevents并添加.xcodeproj文件
  2. 在XCode的项目导航器中,选择您的项目。将来自keyboardevents项目的lib * .a添加到项目的Build Phases➜用库链接二进制文件单击在项目导航器中之前添加的.xcodeproj文件,然后转到Build Settings选项卡。确保已打开“全部”(而不是“基本”)。查找标题搜索路径,并确保它同时包含$(SRCROOT)/../ react-native / React和$(SRCROOT)/../../ React-将二者都标记为递归。
  3. 运行您的项目(Cmd + R)

然后回到javascript领域:

您需要导入react-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
react-native@0.11.0-rc现在通过DeviceEventEmitter发送键盘事件(例如“ keyboardWillShow”),因此您可以为这些事件注册侦听器。但是,在处理ListView时,我发现在ListView的scrollview上调用scrollTo()效果更好:this.listView.getScrollResponder().scrollTo(rowID * rowHeight); 当行的TextInput接收到onFocus事件时,将对其进行调用。
刘德华

4
该答案不再有效,因为RCTDeviceEventEmitter可以完成这项工作。
Sherlock


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
Madura Pradeep


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

@斯蒂芬

如果您不介意不以与键盘显示完全相同的速度设置高度动画,则可以使用LayoutAnimation,这样至少高度不会跳入适当位置。例如

从react-native导入LayoutAnimation并将以下方法添加到您的组件中。

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答案,从react-native 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)'17年

0

我正在使用一种更简单的方法,但尚未启用动画。我有一个名为“ bumpedUp”的组件状态,我默认将其设置为0,但在textInput获得焦点时将其设置为1,如下所示:

在我的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答案来提高滚动视图的底部。然后,我使用onScroll更新滚动视图的当前位置。然后,我发现了这个React Native:获取元素的位置以获取textinput的位置。然后,我做一些简单的数学运算以确定输入是否在当前视图中。然后,我使用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 );
                        }
                     );
                };
            },

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.