使用React Native自定义安全键盘
自定义键盘的实现效果如下:
iOS端:
http://www.jianshu.com/u/b92ab7b3ab48
Android端:
实现功能(iOS和Android):
- 输入框没有自定义,直接使用RN的TextInput
- 点击完成可以移除焦点
- 长按回退键可以删除光标前所有字符
- 字母和字符键盘点击会显示Tip
- 字母、字符、数字键盘可相互快速切换(为视图做了懒加载和缓存)
大致思路
1、如何隐藏系统键盘
用RN为iOS和Android双平台共用一套自定义键盘,使用RN的TextInput作为输入框,这样TextInput该怎么使用就怎么使用,因此问题的关键就在于如何隐藏系统键盘。在iOS中使用inputView
可以轻松实现自定义键盘,在Android中自定义键盘并非像iOS那么直接,需要手动隐藏系统键盘,然后监听输入框的焦点事件来控制自定义键盘的显示和隐藏。
可以看以下两个示例大致了解下:
iOS自定义键盘
Android 自定义键盘实现
Android中自定义键盘有一个让人容易误解的地方,就是
KeyboardView
。可以在Android 自定义键盘实现的Demo中看到有hideSystemSoftKeyboard
方法,用来把EditText的系统键盘给隐藏,然后再使用KeyboardView
加入到视图的底部布局中,KeyboardView
使用KeyboardView.OnKeyboardActionListener
来监听键盘点击,使用Keyboard
来给定键盘布局(使用XML描述布局),然后可以动态修改Keyboard
和KeyboardView.OnKeyboardActionListener
来实现不同的自定义键盘。由此可见,使用RN做自定义键盘在hideSystemSoftKeyboard
后就可以打止了,不需要使用KeyboardView
,而是使用ReactRootView
,然后在RN中绘制键盘。
2、自定义键盘与原生输入组件如何交互
-
RN -> Native
- RN的TextInput要告知Native它需要什么类型的自定义键盘,因此需要把TextInput的ReactTag和键盘类型传给Native。
- RN的键盘每次点击一个按钮需要通知Native,并且传入RN的Keyboard对应的TextInput组件(在Android中是EditText,在iOS中是TextFiled和TextView)的ReactTag,Native可以通过ReactTag来定位到具体的Native输入框,然后再根据RN中传来的命令来修改输入框的内容从而实现同步。
-
Native -> RN
-
iOS:
- Native需要创建一个RCTRootView,然后将一些初始化参数传入(如自定义键盘类型,TextView对应的ReactTag)
- 将RCTRootView赋值给ReactTag对应的TextView的inputView。
-
Android:
- Native拿到ReactTag对应的EditText,禁止它的系统键盘弹出。
- Native需要创建ReactRootView加入到Activity的底部布局中当做键盘使用。
- 将ReactRootView加入到EditText的tag中,然后监听EditText的焦点改变来控制相对应的键盘的显示和隐藏。
-
iOS:
时序图如下:
RN中文网的自定义键盘的GitHub地址:https://github.com/reactnativecn/react-native-custom-keyboard
关于ReactTag的基本原理(iOS版):http://awhisper.github.io/2016/07/02/ReactNative%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%902/
绘制
将上面的流程和代码弄清后就只剩下RN的键盘界面问题了。
数字键盘的布局和绘制:
参考代码:https://github.com/beefe/react-native-keyboard
字母键盘的布局示意图:
字符键盘的布局示意图:
字母和符号键盘的Tip绘制:
最初想法:每个key都为一个单独的component,里面有个TouchableHighlight按钮和与按钮并级的Tip组件,在onPressIn和onPressOut之间控制Tip的显示与隐藏。
不可行:以上方案在Android端不可行,因为Android中RN不能使用overflow:visible属性,在0.41版本中可以使用FlatUIImplement实现overflow:visible属性,但是在0.42及以上版本使用会报错(不过现在解决了,我没试)。
曲线救国想法:使用measureLayout
得到key相对keyboard的位置,然后在keyboard层级绘制Tip,而不在key组件中绘制Tip。这个方案基本可行,但是在Android端还是需要解决以下问题:
1、Tip可能会超过keyboard的边界,因此第一行还是会被截掉,所以需要把键盘的高度要加高一些以容纳第一行的Tip。
2、keyboard的背景色会遮挡下层视图,所以要对keyboard的背景做透明。
3、keyboard加高的部分会遮挡下层视图对手势的响应,所以对加高的部分要放弃事件响应。
绘制难点:
iOS端Tip外形的绘制:使用react-native-svg绘制,我的绘制代码。
Android阴影的实现:使用以react-native-svg为基库做成的react-native-shadow。
注:我并没有在Android中使用阴影样式,而是仿照f8app的写法在不同平台使用不同样式。
性能优化
因为字母和字符键盘一次性绘制了30多个按钮在屏幕上,会导致在低端Android机能感觉到卡顿(调成Debug模式会很明显,在Dev下打开'Show Perf Monitor'会发现首屏渲染时JS线程掉帧很明显),我在这做了个测试:一次性渲染40个按钮,可以运行看一下卡顿情况。
因此渲染出的数字、字母和字符键盘的DOM分别要被缓存起来,这样才不会在频繁切换键盘时感觉到卡顿。
如果首屏出现的是数字键盘,那么字母键盘和字符键盘就不应该被放在渲染树中,做到用到特定键盘时再去加载这个键盘。
根据React 源码剖析系列 - 不可思议的 react diff,要做到高效的渲染尽量不能改变Dom树的结构,React并没有提供
removeView
和addView
之类的方法(当然你可以通过带_
的私有方法实现),Element(使用JSX或者React. createElement创建出来的)其实并不占多大空间,真正渲染的是DOM(可以通过ref获取),使用DisplayView可以控制DOM的隐藏和显示,让视图在第一次使用时才在DOM树创建,在隐藏和显示时不用改变DOM树的结构。
示例代码
安利两个工具
react-native-storybook
UI组件的开发工具,可以浏览组件库,查看各个组件的各个状态,以及开发和测试组件。