之前记录的一篇android使用扫码枪被系统键盘拦截的问题 2019-08-13存在误导,这里重新记录下问题解决方案。
场景
android端外接扫码枪、键盘等输入设备,在完全没有input
组件的时候,如果按键输入一些字符,会发生什么情况呢?原生安卓,可能并没有大问题,但是如果你是使用了webview
呢?
可以简单测试下,就一个空的activity
,嵌入一个WebView
,什么都不加载,使用外接键盘输入字符,你会发现软键盘莫名其妙的弹出来了???(版本是android5.1,6.0以上的好像就不会)。
并且弹出键盘会导致WebView
的onkeypress
方法接收不到输入事件,无法进行之后的一系列操作。
至于什么问题,很遗憾,我查阅了一些资料也没找到答案。
解决思路
由于是WebView
导致的键盘弹出,那索性就让其没有焦点,这总不会弹出键盘了吧。
webView.setFocusable(false);
加上以上代码,确实键盘也不弹出了,但是WebView
也收不到输入事件了。既然如此,那就由android端拦截键盘输入再转发给web不就好了?
那就重写activity
的dispatchKeyEvent
方法,所有的输入事件都会经过它分发出去。其boolean
类型的返回值,返回true
表示事件已经被我处理了,你不要再传给其他人了;返回false
则表示,我不处理,你传给其他人处理吧。
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
checkLetterStatus(event);
if (event.getAction() == KeyEvent.ACTION_DOWN) {
char ch = getInputCode(event);
if(ch == 0){
return super.dispatchKeyEvent(event);
}else{
String codeStr = String.valueOf(ch);
this.callWebHandler(JsMessage.ON_KEYCODE_EVENT, new WebKeyEvent(keyCode,codeStr));
return true;
}
}
return super.dispatchKeyEvent(event);
}
使用checkLetterStatus
方法记录shift状态,再判断是否为KeyEvent.ACTION_DOWN
按下按键时,因为一次按键包含两个操作:keydown和keyup,也会触发两次dispatchKeyEvent
,所以需要做一次判断隔离。然后getInputCode
方法筛选需要的字符,不需要的字符则返回0,抛给父类处理,需要的字符则通过callWebHandler
直接传给了Web。
checkLetterStatus
的实现很简单,单纯的记录shift的按下状态
private Boolean mCaps = false;
private void checkLetterStatus(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//按着shift键,表示大写
mCaps = true;
} else {
//松开shift键,表示小写
mCaps = false;
}
}
}
getInputCode
方法则主要负责将keyCode
转成字符
private char getInputCode(KeyEvent event) {
int keyCode = event.getKeyCode();
char aChar = 0;
if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
//字母
aChar = (char) ((mCaps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
} else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//数字
aChar = caseNumber(keyCode);
} else {
//其他符号
switch (keyCode) {
case KeyEvent.KEYCODE_PERIOD:
aChar = mCaps?'>':'.';
break;
case KeyEvent.KEYCODE_MINUS:
aChar = mCaps ? '_' : '-';
break;
case KeyEvent.KEYCODE_SLASH:
aChar = mCaps?'?':'/';
break;
case KeyEvent.KEYCODE_BACKSLASH:
aChar = mCaps ? '|' : '\\';
break;
case KeyEvent.KEYCODE_NUMPAD_MULTIPLY:
aChar = '*';
break;
case KeyEvent.KEYCODE_ENTER:
aChar = '\n';
break;
case KeyEvent.KEYCODE_EQUALS:
aChar = mCaps?'+':'=';
break;
default:
break;
}
}
return aChar;
}
如果输入是字母,就用keyCode
减去A键的值KeyEvent.KEYCODE_A
,再根据之前记录的mCaps
值加回字符'A'或者'a'控制其大小写。如果是数字,则使用caseNumber
方法返回对应的字符。其他字符,则使用了switch
一个个列举,根据mCaps
的值看着键盘一个个敲。
caseNumber
方法如下实现
private char caseNumber(int keyCode){
char ch = (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
if(!mCaps){
return ch;
}
switch (keyCode){
case KeyEvent.KEYCODE_0:return ')';
case KeyEvent.KEYCODE_1:return '!';
case KeyEvent.KEYCODE_2:return '@';
case KeyEvent.KEYCODE_3:return '#';
case KeyEvent.KEYCODE_4:return '$';
case KeyEvent.KEYCODE_5:return '%';
case KeyEvent.KEYCODE_6:return '^';
case KeyEvent.KEYCODE_7:return '&';
case KeyEvent.KEYCODE_8:return '*';
case KeyEvent.KEYCODE_9:return '(';
}
return ch;
}
没有shift按键操作,则直接返回ch
;否则,就列举0-9所有的上行符号。
安卓端已经处理完毕,接下来就是web端的逻辑
onAndroidKeyEvent(keyObj){
if(!this.allowInput || this.state.visible){
// 不允许输入及结算流程,不处理输入
return;
}
// console.log("keyCode = ", keyCode);
let num = keyObj.keyCode;
console.log("num = ", num);
if (num !== 66) {
if(!this.codeStr || this.codeStr.length === 0){
// 输入超时
setTimeout(()=>{
this.codeStr = "";
}, 1000);
}
// this.codeStr += String.fromCodePoint(num);
this.codeStr += keyObj.codeStr;
} else {
this.allowInput = false;
setTimeout(()=>{
this.allowInput = true;
},1000);
let value = this.codeStr;
this.codeStr = "";
// ... do some next
}
}
其实也没啥逻辑,就是加了个超时,1秒内没有输入回车则丢弃。66
是android端回车的keyCode。
至此,web端已经能接收到外接设备的输入值,并且也不再会弹出键盘。
网友评论