ReactNative踩坑之TextInput

身为一个web开发者,从来没想到自己会被一个小小的文本输入框折腾成这样。

ReactNative踩坑之TextInput_第1张图片

最近在使用TextInput组件过程中,遇到的一些问题,在此做个分享和记录。

1.键盘遮挡

在web开发时,我们不需要考虑键盘的遮挡,因为系统会自动处理,但是在native这方面需要手动处理,还好官方已经出品了原生解决方案:KeyboardAvodingView

android可以通过设置android:windowSoftInputMode="adjustPan"系统自动处理键盘遮挡,这种情况下不需要使用该组件

先上代码

 
      
        {/*.....*/}
      
    

看下文档便知,比较有用的就是这两个属性了

  • keyboardVerticalOffset
  • behavior

keyboardVerticalOffset

有些情况下你的视图可能距离屏幕顶部有一段距离,这个时候需要用这个进行修正。

网上教程很少详细说这个属性,但是这个属性非常重要。

先让我们看下KeyboardAvoidingView源码的实现

relativeKeyboardHeight(keyboardFrame: ScreenRect): number {
    const frame = this.frame;
    if (!frame || !keyboardFrame) {
      return 0;
    }

    const keyboardY = keyboardFrame.screenY - this.props.keyboardVerticalOffset;

    // Calculate the displacement needed for the view such that it
    // no longer overlaps with the keyboard
    return Math.max(frame.y + frame.height - keyboardY, 0);
},

onKeyboardChange(event: ?KeyboardChangeEvent) {
    if (!event) {
      this.setState({bottom: 0});
      return;
    }

    const {duration, easing, endCoordinates} = event;
    const height = this.relativeKeyboardHeight(endCoordinates);

    if (duration && easing) {
      LayoutAnimation.configureNext({
        duration: duration,
        update: {
          duration: duration,
          type: LayoutAnimation.Types[easing] || 'keyboard',
        },
      });
    }
    this.setState({bottom: height});
  },

可以看到该组件其实是在键盘打开关闭状态改变的时候,更新了botttom这个状态,而这个值是通过坐标计算而来的。

this.frame 是组件的根节点View onLayout回调得到值。

这里先用虚拟键盘在屏幕的y坐标减去了传入的keyboardVerticalOffset获取到键盘的y轴偏移量,然后frame.y + frame.height计算出容器距离父元素顶部的距离,减去键盘的y轴偏移量,就得出了容器需要偏移的距离。

为什么要减去一个偏移量呢?打个比方,我们的页面都会有状态栏,会有头部(例如使用react-navigation),这个时候我们在使用KeyboardAvoidingView的时候,可能并没有从屏幕顶部进行包裹,因为onLayout得到的y坐标是相对于父元素的,所以就会造成键盘的screenYframe.y没有以一个起点作为参考,所以需要手动修正这一块距离。

其实把计算改成:

    return Math.max(frame.y + this.props.keyboardVerticalOffset + frame.height  -  keyboardFrame.screenY, 0);

这样就比较好理解了。

behavior

顾名思义就是以何种方式处理,看源码得知主要做了如下处理

  • height
    键盘打开时把容器的高度减去了上面计算出来的bottom的值,关闭时又恢复原来大小
  • padding
    键盘打开时给容器设置了paddingBottom: bottom
  • position
    键盘打开时给容器设置了bottom:bottom,也就是让容器向上偏移bottom大小距离

到底使用哪一个呢,个人觉得还是看使用场景吧。

问题

如果我们的输入框在一个ScrollView里面,像我们上面写的那个demo一样,而KeyboardAvoidingView在底部加入了paddingBottom,仅仅是压缩了ScrollView的高度,这样本来位置靠下的输入框能显示吗?

结果是可以。

如图所示,ScrollView自动就滚到输入框的位置了。

这也是最令人困惑的地方。同事说可能是react-native内部做了什么处理。希望研究过的同学解答下。

自动滚动

2.android设置textAlign引发的bug

BUG-1

如果同时设置了textAlignheight属性,那么只要手指在TextInput上横向滑动几下,placeholder就不见了,光标也不见了,键盘却能弹出,也能正常输入,但是不显示,用手指再去触摸一下,文字就出来了。。。

ios无此问题。

解决办法:对android不设置高度。

BUG-2

https://github.com/facebook/react-native/issues/12167
https://github.com/facebook/react-native/issues/15764
https://github.com/facebook/react-native/issues/17135

当对TextInput组件设置textAlignright或者center时,当手指触摸到TextInput上方时,外层的ScrollView将中断滚动,而且TextInput会获取到焦点,键盘弹出。当textAlignleft时则不会有此问题。这个问题仅在android手机上会出现。

提出这个问题的人不少,但是没有找到官方的解决方案。

有人指出使用multilinekeyboardType: default 可以解决,尝试了下确实可以,但是实际上我们可能不需要multiline,而且keyboardType为其他类型时,placeholder的文字就会显示了,简直是个迷。。。

还好有人在提出了一个hack写法。

focusInput = () => {
    const { textInput } = this;
    if (!textInput) return;
    if (textInput.isFocused()) {
      textInput.blur();
      setTimeout(() => textInput.focus(), 500);
    } else {
      textInput.focus();
    }
  }
...

   
  

原理就是屏蔽了textinput本身的手势,在上面覆盖一个手势遮罩,自己处理textInput的获取焦点和失去焦点。经过尝试,这种方案感觉是可行的,目前还没发有发现明显的问题。

======= 6月20日更新 ========

最新发现了一个问题,由于textinput被覆盖了,导致无法进行长按复制粘贴了=。=||。

于是修改了一些逻辑,即在获取焦点后,让覆盖层消失。

持续更新。。。

你可能感兴趣的:(ReactNative踩坑之TextInput)