Flutter_Webview 键盘弹出问题
webview_flutter ^0.3.7+1
pub链接
webview_flutter在Android上没有办法弹出键盘,github上的issue已经提了很久,但是官方的milestone还要到19年的十月 issue #19718 ,(截止发稿时已经有一个PR到master分支了,但是stable分支的同学可能就还要等一哈了),但是PR的解决方案在AndroidN之前并没有用…comment
1.来自其他同学的启发
隐藏TextField方案 ,这个方案的简单思路就是在onPageFinish
时给Webview注入一段js代码,监听input/textarea的focus事件,然后通过JSChannel发送给隐藏在WebView之后的TextField,通过TextField间接唤起软键盘然后,通过监听TextField的onChange事件给input/textarea设置新的值
下面是他的JS代码实现:他用js监听某个网站的几个需要弹出输入法的element,focus事件/focusout事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 inputs = document .getElementsByClassName ('search-bar' ); header=document .getElementsByClassName ('site-header' ); header[0 ].style .display = 'none' ; buttons = document .getElementsByClassName ('icon' ); buttons[0 ].focus (); if (inputs != null ) { input = inputs[0 ]; InputValue .postMessage (input.value ); input.addEventListener ('focus' , (_ ) => { Focus .postMessage ('focus' ); }, true ); input.addEventListener ('focusout' , (_ ) => { Focus .postMessage ('focusout' ); }, true ) }
JSChannel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 javascriptChannels: Set .from( [ JavascriptChannel( name: 'Focus' , onMessageReceived: (JavascriptMessage focus) { print (focus.message); if (focus.message == 'focus' ) { FocusScope.of(context) .requestFocus(_focusNode); } else if (focus.message == 'focusout' ) { _focusNode.unfocus(); } }, ), JavascriptChannel( name: 'InputValue' , onMessageReceived: (JavascriptMessage value) { _textController.value = TextEditingValue(text: value.toString()); }, ) ], ),
接收事件更改TextField的text,通过focusNode来唤起/隐藏 软键盘
这个方案看起来很好,但是手写监听所有Classname可太笨了…而且如果用户在弹出键盘后手动关闭键盘(按软键盘上的关闭按钮),软键盘就再也弹不出来了…
2.自己的方案升级
用两个TextField交替接收事件来解决无法在手动关闭键盘后重新唤起的问题
注入JS监听所有input/textarea element,不监听focus/focusout事件,监听click事件,有几个好处1. 可以获取用户当前文字选择/光标位置
2. 可以判断再次点击事件
记录当前focus的element,用于判断是否需要重新唤起新的键盘
下面的简单的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 var inputs = document .getElementsByTagName ('input' );var textArea = document .getElementsByTagName ('textarea' );var current;for (var i = 0 ; i < inputs.length ; i++) { console .log (i); inputs[i].addEventListener ('click' , (e ) => { var json = { "funcName" : "requestFocus" , "data" : { "initText" : e.target .value , "selectionStart" :e.target .selectionStart , "selectionEnd" :e.target .selectionEnd } }; json.data .refocus = (document .activeElement == current); current = e.target ; var param = JSON .stringify (json); console .log (param); UserState .postMessage (param); }) } for (var i = 0 ; i < textArea.length ; i++) { console .log (i); textArea[i].addEventListener ('click' , (e ) => { var json = { "funcName" : "requestFocus" , "data" : { "initText" : e.target .value , "selectionStart" :e.target .selectionStart , "selectionEnd" :e.target .selectionEnd } }; json.data .refocus = (document .activeElement == current); current = e.target ; var param = JSON .stringify (json); console .log (param); UserState .postMessage (param); }) }; console .log ('===JS CODE INJECTED INTO MY WEBVIEW===' );
UserState是定义好的jsChannel,接收一个jsonString形式的参数,data.initText是初始文字,selectEnd selectStart是文字选择位置,refocus是判断是否重新点击focus的Element的标记,element被点击时:
* 先判断是不是refocus,发送给channel
接下来要处理JSChannel收到的数据了,其中showAndroidKeyboard是自己编写的一个PlatformChannel,用来显示软键盘,代码还可以简化,我这里就不写了,有改进的请和作者联系…也是在踩坑中.
关键是TextField交替获取焦点,当TextField - A获取焦点,进行一番编辑之后,用户手动关闭了软键盘,这个时候FocusScop.of(context).requestFocus(focusNodeA)是无法唤起键盘的,所以需要另一个TextField-B来请求焦点,
showAndroidKeyboard()是为了在某些情况下备用focusNode也没获取焦点,需要我们手动唤起焦点
隐藏键盘的事件还没有很好的解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 bool refocus = data['refocus' ] as bool ; if (_focusNode2.hasFocus) { if (!refocus) FocusScope.of(context).requestFocus(_focusNode1); if (!_focusNode1.hasFocus) { showAndroidKeyboard(); } String initText = data['initText' ]; var selectionStart = data['selectionStart' ]; var selectionEnd = data['selectionEnd' ]; int end = initText.length; int start = initText.length; if (selectionEnd is int ) { end = selectionEnd; } if (selectionStart is int ) { start = selectionEnd; } print (selectionEnd); textController1.value = TextEditingValue( text: initText, selection: TextSelection(baseOffset: start, extentOffset: end), ); FocusScope.of(context).requestFocus(_focusNode1); } else { if (!refocus) FocusScope.of(context).requestFocus(_focusNode2); if (!_focusNode2.hasFocus) { showAndroidKeyboard(); } String initText = data['initText' ]; var selectionStart = data['selectionStart' ]; var selectionEnd = data['selectionEnd' ]; int end = initText.length; int start = initText.length; if (selectionEnd is int ) { end = selectionEnd; } if (selectionStart is int ) { start = selectionEnd; } print (selectionEnd); textController2.value = TextEditingValue( text: initText, selection: TextSelection(baseOffset: start, extentOffset: end), ); FocusScope.of(context).requestFocus(_focusNode2); }
webview后面隐藏的TextField
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 return Stack( children: <Widget>[ TextField( autofocus: false , focusNode: _focusNode1, controller: textController1, onChanged: (text) { controller.evaluateJavascript(''' if(current != null){ current.value = '${textController1.text} '; current.selectionStart = ${textController1.selection.start} ; current.selectionEnd = ${textController1.selection.end} ; current.dispatchEvent(new Event('input')); } ''' ); }, onSubmitted: (text) { controller.evaluateJavascript(''' if(current != null) current.submit(); ''' ); _focusNode1.unfocus(); }, ), TextField( autofocus: false , focusNode: _focusNode2, controller: textController2, onChanged: (text) { controller.evaluateJavascript(''' if(current != null){ current.value = '${textController2.text} '; current.selectionStart = ${textController2.selection.start} ; current.selectionEnd = ${textController2.selection.end} ; current.dispatchEvent(new Event('input')); } ''' ); }, onSubmitted: (text) { controller.evaluateJavascript(''' if(current != null) current.submit(); ''' ); _focusNode2.unfocus(); }, ), WebView(....) ] );
在TextField的onChange中改编当前focus的Element的text/selection,然后发送一个input事件来触发Element改变current.dispatchEvent(new Event('input'));
,这里的大家应该都很好理解了.
不得不说谷歌太坑了,webview竟然有这样的大缺陷,不过也没办法毕竟版本号还没到1.0.0呢…本文的方法只是一个临时的解决方案,比如点击空白隐藏软键盘等功能还没实现,只使用一些简单的文本输入的webview还是可以的,还有一些input标签作为按钮的可能还要手动隐藏下键盘,以上.