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

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

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

谁有好的解决方案?

【问题讨论】:

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

标签: javascript react-native


【解决方案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'stackoverflow.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

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

【讨论】:

  • 看到代码示例/有关如何制作动画的更多信息会很棒。跳转非常笨拙,并且仅使用“将显示”和“确实显示”方法,我无法完全弄清楚如何猜测键盘动画将多长时间或从“将显示”到多高。
  • 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) stackoverflow.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 页面链接。

      【讨论】:

      【解决方案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 来获取真实高度。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-04-25
                • 1970-01-01
                • 2018-03-03
                • 2011-11-09
                • 2020-12-06
                • 1970-01-01
                相关资源
                最近更新 更多