自定義鍵盤的實現效果如下:
iOS端:
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地址:github.com/reactnative…
關於ReactTag的基本原理(iOS版):awhisper.github.io/2016/07/02/…
繪製
將上面的流程和程式碼弄清後就只剩下RN的鍵盤介面問題了。
數字鍵盤的佈局和繪製:
參考程式碼:github.com/beefe/react…
字母鍵盤的佈局示意圖:
字元鍵盤的佈局示意圖:
字母和符號鍵盤的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加高的部分會遮擋下層檢視對手勢的響應,所以對加高的部分要放棄事件響應。
解答參考了stackoverflow上的提問:Element overflow hidden in React-Native Android
繪製難點:
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元件的開發工具,可以瀏覽元件庫,檢視各個元件的各個狀態,以及開發和測試元件。
歡迎關注我的簡書主頁:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^