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

Posted

技术标签:

【中文标题】当 TextInput 有焦点时,如何从键盘后面自动滑出窗口?【英文标题】:How to auto-slide the window out from behind keyboard when TextInput has focus? 【发布时间】:2015-06-01 12:32:32 【问题描述】:

我已经看到了本机应用程序自动滚动窗口的这种 hack,但想知道在 React Native 中执行此操作的最佳方法...当 <TextInput> 字段获得焦点并位于视图中的较低位置时,键盘将覆盖文本字段。

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

谁有好的解决方案?

【问题讨论】:

我建议将此作为问题添加到 Github 跟踪器上,看看是否有任何结果,因为这将是一个非常常见的投诉。 【参考方案1】:

2017 年答案

KeyboardAvoidingView 可能是现在最好的方式。查看文档here。与Keyboard 模块相比,它非常简单,它让开发人员可以更好地控制执行动画。 Spencer Carli 在his medium blog 上展示了所有可能的方法。

2015 年答案

react-native 中执行此操作的正确方法不需要外部库,利用本机代码并包含动画。

首先定义一个函数来处理每个TextInput(或您想要滚动到的任何其他组件)的onFocus 事件:

// 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 设计的需求。

【讨论】:

这是一个不错的发现,但对我来说这还不够,因为虽然 ScrollView 将确保 TextInput 在屏幕上,但 ScrollView 仍在键盘下方显示用户无法显示的内容'滚动到。设置 ScrollView 属性“keyboardDismissMode=on-drag”允许用户关闭键盘,但如果键盘下方没有 足够 滚动内容,体验会有点不协调。如果 ScrollView 只需要首先因为键盘而滚动,并且您禁用弹跳,那么似乎没有办法关闭键盘并显示下面的内容 @miracle2k - 我有一个功能可以在输入模糊时重置滚动视图位置,即当键盘关闭时。也许这对你的情况有帮助? @Sherlock 模糊滚动视图重置功能是什么样的?顺便说一句很棒的解决方案:) 在较新的 React Native 版本中,您需要调用: * import ReactNative from 'react-native'; * 在调用之前 * ReactNative.findNodeHandle() * 否则应用会崩溃 现在import findNodeHandle from 'react-native'***.com/questions/37626851/…【参考方案2】:

Facebook open sourced KeyboardAvoidingView 在 react native 0.29 中解决了这个问题。文档和使用示例可以在here找到。

【讨论】:

当心KeyboardAvoidingView,它就是不好用。它并不总是像您期望的那样运行。文档几乎不存在。 文档和行为现在越来越好了 我遇到的问题是,KeyboardAvoidingView 在我的 iPhone 6 模拟器上将键盘高度测量为 65,因此我的视图仍然隐藏在键盘后面。 我能管理它的唯一方法是通过DeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));触发的底部填充方法 在 React Native v0.65 中,这里是 KeyboardAvoidingView【参考方案3】:

我们结合了 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 模拟器中显示? 你试过Command+K(硬件->键盘->切换软件键盘)吗? 在这里尝试修改版本:gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 我相信这会更好,因为它不依赖任何我认为是脆弱的 imo 的超时。【参考方案4】:

首先你需要安装react-native-keyboardevents。

    在 XCode 中,在项目导航器中,右键单击库 ➜ 添加 文件到 [您的项目名称] 转到 node_modules ➜ react-native-keyboardevents 并添加 .xcodeproj 文件 在 XCode 中,在 项目导航器,选择您的项目。从键盘事件添加 lib*.a 项目到您项目的构建阶段➜链接二进制与库单击 您之前在项目导航器中添加的 .xcodeproj 文件并转到 Build 设置选项卡。确保打开“全部”(而不是“基本”)。 查找标题搜索路径并确保它包含两者 $(SRCROOT)/../react-native/React 和 $(SRCROOT)/../../React - 标记 都是递归的。 运行您的项目 (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,但为了简单起见,我们只是在动画之后进行调整。

【讨论】:

看到代码示例/有关如何制作动画的更多信息会很棒。跳转非常笨拙,并且仅使用“将显示”和“确实显示”方法,我无法完全弄清楚如何猜测键盘动画将多长时间或从“将显示”到多高。 react-native@0.11.0-rc 现在通过 DeviceEventEmitter 发送键盘事件(例如,“keyboardWillShow”),因此您可以为这些事件注册监听器。然而,在处理 ListView 时,我发现在 ListView 的滚动视图上调用 scrollTo() 效果更好:this.listView.getScrollResponder().scrollTo(rowID * rowHeight); 当它接收到 onFocus 事件时,它会在一行的 TextInput 上调用。 这个答案不再有效,因为 RCTDeviceEventEmitter 完成了这项工作。【参考方案5】:

react-native-keyboard-aware-scroll-view 为我解决了这个问题。 react-native-keyboard-aware-scroll-view on GitHub

【讨论】:

【参考方案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) ***.com/a/35874233/3346628 使用 this.keyboardWillHide.bind(this) 而不是 self 使用键盘代替 DeviceEventEmitter【参考方案7】:

只是想提一下,现在 RN 中有一个 KeyboardAvoidingView。只需将其导入并用作 RN 中的任何其他模块即可。

这是 RN 上提交的链接:

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

从 0.29.0 开始提供

他们还在 UIExplorer 上提供了一个示例。

【讨论】:

【参考方案8】:

也许为时已晚,但最好的解决方案是使用原生库,IQKeyboardManager

只需将 IQKeyboardManager 目录从演示项目拖放到您的 iOS 项目。而已。您还可以设置一些值,例如启用 isToolbar,或者在 AppDelegate.m 文件中设置文本输入和键盘之间的空间。有关自定义的更多详细信息,请参见我添加的 GitHub 页面链接。

【讨论】:

这是一个很好的选择。另请参阅 github.com/douglasjunior/react-native-keyboard-manager 了解为 ReactNative 包装的版本 - 它易于安装。【参考方案9】:

我使用了 TextInput.onFocus 和 ScrollView.scrollTo。

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

【讨论】:

【参考方案10】:

@斯蒂芬

如果您不介意高度的动画效果与键盘出现的速度完全相同,您可以使用 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 开始,可以使用内置功能解决键盘大小调整问题。

【讨论】:

【参考方案11】:

您可以将一些方法组合成更简单的方法。

在你的输入上附加一个 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);
    );

这告诉我们的滚动视图(记得添加一个 ref)向下滚动到我们聚焦输入的位置 - 200(它大约是键盘的大小)

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


componentWillUnmount() 
    this.keyboardDidHideListener.remove()


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

这里我们将滚动视图重置回顶部,

【讨论】:

@能否请您提供您的 render() 方法?【参考方案12】:

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

在我的文本输入上:

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

我还有一种样式,可以为屏幕上所有内容的包装容器提供下边距和负上边距,如下所示:

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

然后在包装容器上,我这样设置样式:

&lt;View style=[styles.mythingscontainer, this.state.bumpedUp &amp;&amp; styles.bumpedcontainer]&gt;

因此,当“bumpedUp”状态设置为 1 时,bumpedcontainer 样式就会启动并将内容向上移动。

有点老套,边距是硬编码的,但它可以工作:)

【讨论】:

【参考方案13】:

我使用 brysgo 答案来提升滚动视图的底部。然后我使用 onScroll 更新滚动视图的当前位置。然后我找到了这个React Native: Getting the position of an element 来获取文本输入的位置。然后我做一些简单的数学运算来确定输入是否在当前视图中。然后我使用 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 );
                        
                     );
                ;
            ,

【讨论】:

【参考方案14】:

我也遇到过这个问题。最后我通过定义每个场景的高度来解决,比如:

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

而且,我还使用第三方模块https://github.com/jaysoo/react-native-extra-dimensions-android 来获取真实高度。

【讨论】:

以上是关于当 TextInput 有焦点时,如何从键盘后面自动滑出窗口?的主要内容,如果未能解决你的问题,请参考以下文章

react native 隐藏键盘 TextInput失去焦点

在关注textinput时,保持textinput和键盘的小差距

在本机反应中单击 TextInput 字段外部时失去焦点并关闭键盘?

在输入之外单击时,RN TextInput 不会失去焦点并隐藏它的键盘

单击“TextInput”对象后在 Kivy 中重新获得键盘焦点

反应本机文本输入多行没有被键盘向上推