大家想不想要这样一台Android  Surface平板,看着就过瘾吧。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSVRsZWFrcw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast">

我们知道,android眼下的输入都是通过软键盘实现的,用外接键盘的少。这个在手机上是能够理解的。当手机接上外接键盘后。总体会显得头重脚轻。而且用键盘输入时。人离手机的距离就远了,自然不太适合看清手机上的内容。那在平板上呢?假设平板仅仅是平时用来浏览看视频,不进行大量输入。自然也用不上外接键盘。

那到底什么时候须要用到外接键盘呢?本人认为首先要满足例如以下两个条件。

1)   平板和外接键盘完美融合,组合后非常像笔记本使用模式。类似上面Android Surface的机器,平板和键盘通过磁性自己主动粘合,变身笔记本模式

2)    Android用在类办公等须要高速输入场景,比方写文章。长时间聊qq等。事实上linux一直以来没法进入桌面系统的关键原因是window在这方面太优秀,它垄断了用户的办公习惯,即用Microsoft office系列软件办公。可是如今类linux。尤其Android在这边已经有了非常大进步,一方面,ubuntu帮组linux积累了一部分用户。比方libre office体验好多了。同一时候据说微软正在为Android开发Microsoft office的响应产品,这个是利好消息。

从上面看来。事实上市面上已经有满足上面两个条件的机器了。比方联想的A10        

它是一台超级本, 但它支持翻转,当翻转过来就是平板。

那为啥这样的Android超极本就不够火呢?当然有非常多原因啊,比方平板本身需求量小,Android本身就不适合办公。当然肯定也有另外一个小原因。它这个物理键盘居然不能中文输入。

因此,Android平板要进入办公领域并流行,须要实现类似PC端中文输入的体验。

本文说到的外接键盘中文输入,重在中文两字。其实,Android本身是支持外接键盘的。可是仅仅可以实现英文输入。其实。我们在前几篇文章已经说到了输入法,也已经分析到,Android要想输入中文,必须通过输入法。

那为啥Android的中文输入法不能像PC那样直接通过外接键盘输入呢?以下一一分析。

Android没法通过外接键盘中文输入原因

输入法和外接键盘不能共存

Android系统里,当有外接键盘时。输入法就会消失。这样自然没法通过输入法输入中文。

这个是由Configuration的keyboard配置项决定的。正常情况下。Configuration的keyboard值是nokeys,而当系统检測到外接键盘(蓝牙键盘等等)插入时,就会更新系统的Configuration,并将当中的keyboard置为非nokeys(比方Configuration.KEYBOARD_QWERTY),然后系统会将新的Configuration通知给全部程序,包含输入法。

当输入法程序检測到新的Configuration时,它会运行更新操作,然后发现已经有外接设备就会隐藏自己。这样输入法就不见了。

详细逻辑例如以下:

    //系统端 :WindowManagerService.java
boolean computeScreenConfigurationLocked(Configuration config, boolean forceRotate) {
final InputDevice[] devices = mInputManager.getInputDevices();
final int len = devices.length;
for (int i = 0; i < len; i++) {
InputDevice device = devices[i];
if (!device.isVirtual()) {
final int sources = device.getSources();
final int presenceFlag = device.isExternal() ?
WindowManagerPolicy.PRESENCE_EXTERNAL :
WindowManagerPolicy.PRESENCE_INTERNAL; if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
//检測到外接键盘
config.keyboard = Configuration.KEYBOARD_QWERTY;
keyboardPresence |= presenceFlag;
}
}
} // Determine whether a hard keyboard is available and enabled.
boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
if (hardKeyboardAvailable != mHardKeyboardAvailable) {
mHardKeyboardAvailable = hardKeyboardAvailable;
mHardKeyboardEnabled = hardKeyboardAvailable;
mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
}
if (!mHardKeyboardEnabled) {
config.keyboard = Configuration.KEYBOARD_NOKEYS;
}
}
return true;
} //输入法端: InputMethodService.java
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); if (visible) {
if (showingInput) {
// onShowInputRequested就会影响输入法的显示
//当有外接键盘时,它会返回false
if (onShowInputRequested(showFlags, true)) {
showWindow(true);
} else {
doHideWindow();
}
}
// onEvaluateInputViewShown也会影响输入法的显示
//当有外接键盘时,它会返回false
boolean showing = onEvaluateInputViewShown();
mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ?
IME_VISIBLE : 0), mBackDisposition);
}
} public boolean onEvaluateInputViewShown() {
Configuration config = getResources().getConfiguration();
//检測Configuration是否标示了有外接键盘
return config.keyboard == Configuration.KEYBOARD_NOKEYS
|| config.hardKeyboardHidden ==
Configuration.HARDKEYBOARDHIDDEN_YES;
} public boolean onShowInputRequested(int flags, boolean configChange) {
if (!onEvaluateInputViewShown()) {
return false;
}
if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
Configuration config = getResources().getConfiguration();
//检測Configuration是否标示了有外接键盘
if (config.keyboard != Configuration.KEYBOARD_NOKEYS) {
return false;
}
}
if ((flags&InputMethod.SHOW_FORCED) != 0) {
mShowInputForced = true;
}
return true;
}

输入法没法获得按键事件

我们知道,假设要想输入法通过外接键盘输出中文,它肯定须要从外接键盘读取到英文输入。而在Android系统中,按键等key事件仅仅发送给焦点程序,可是输入法本身没法获得焦点,因此它自然就没法读取到外接键盘的输入。

问题的解决

让输入法和外接键盘共存

从上面的分析可知。输入法和外接键盘没法共存的根本原因是,输入法会读取configuration里的键盘属性值。

解决问题有两个方法:

1)  改动用到Configuration的相关函数,比方onEvaluateInputViewShown ,onShowInputRequested函数的实现

这种方法看起来可行,可是不行。由于非常多地方可能用到了这个Configuration,改动量比較大,且非常多函数并不是protected或者public,子类是没法直接改动的。

2)  改动输入法的Configuration的值

这种方法可行。从源头上攻克了这个问题,这样InputMethodService觉得系统没有外接键盘。自然就不会隐藏输入法了。

方法2详细实现例如以下:

在输入法初始化和更新Configuration的点主动改动输入法的Configuration。

public class RemoteInputMethod extends InputMethodService {
@Override
public void onCreate() {
super.onCreate();
updateResources();
} @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateResources();
} public void updateResources() {
Configuration config = new Configuration(getResources().getConfiguration());
//改动Configuration,让输入法觉得系统中没有外接键盘
config.keyboard = Configuration.KEYBOARD_NOKEYS;
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
}

让输入法获取外接键盘输入


输入法实现输入有两部分。一是获取按键事件。二是获取输入目标

获取按键事件


上面已经提到过。输入法window是没法获取外接键盘事件的。怎么办?非常好办,让输入法service创建另外一个普通的window(本文称作bridge window),并将这个window标示为可接受key事件的window,当它是最top的可接受key事件的window时, 它就能够获得焦点并获得外接键盘的输入。

这样,它作为中间桥梁就能将外接键盘事件传给输入法
(同一程序里,非常好做的)。输入法然后进行翻译,比方拼音转为中文。

获取并更新输入目标

输入法的输入目标是textView的通信接口InputConnection。它是在程序获得焦点时候或焦点程序中的焦点view发生变化的时候。焦点程序传递给输入法的。

所以,问题来了?一旦上面的bridge window获得焦点后,输入法的输入目标就跟着更新了,变成了bridge window的view的InputConnection。这样即使输入法完毕了英文到中文的转换,最后也仅仅能将中文发送给bridge window,并不能发送给用户想输入的程序。怎么解?还好Android系统有一个特殊window flag-----WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,当一个window设置了这个flag,
它成为焦点时。输入法并不会将输入目标切换为当前焦点window的InputConnection,而是仍旧保持原来的InputConnection。这为我们带来了希望,也就是说,我们仅仅需将我们的bridge window加入这个flag就可以,其实确实如此。

可是还存在一个问题。我们知道InputConnection是相应textView的一个通信接口,当用户改变输入view时,输入法中的InputConnection是须要改动的,可是如今因为目标程序已经不是焦点程序了,当用户触摸目标程序其它textView导致输入view改变时,系统并不会通知输入法去更新InputConnection,这样一来,输入法的中文始终仅仅能传递给一个textView了。

又怎么解呢?灵光一动,继续解。当用户触摸时。我们能够让bridge
window临时失去焦点,这样目标程序就又一次获取了焦点,然后输入view切换时,输入法就能得到通知,也就是能又一次获取到新的textView的InputConnection。然后。bridge window又一次获取焦点,也就是非常短时间后它继续能够接受外接键盘的输入了。

这个方法的重点在bridge window的实现:实现的重点有两个:

1)     加入WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM flag

2)  监听OUT_SIDE事件,这样,当用户单击目标程序。切换焦点view时,bridge window可以提前获知,然后释放焦点,

让目标程序成为焦点,然后完毕焦点view的切换,进而完毕输入法中的输入目标InputConnection的更新。

   public class BridgeWindow extends Dialog {
private static final boolean DEBUG = false;
private static final String TAG = "MDialog"; private static final int flagsNask = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; private static final int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
private static final int flags_nofocus = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; private Window mWindow = null;
private Handler mHandler = new Handler();
private MInputMethod mAttachedInputMethod = null; public BridgeWindow (Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
} public void setAttachedInputMethod(MInputMethod inputMethod) {
mAttachedInputMethod = inputMethod;
} View mRootView = null;
public void setContentView(View view) {
super.setContentView(view);
mRootView = view;
} private void init() {
// TODO Auto-generated method stub
requestWindowFeature(Window.FEATURE_NO_TITLE);
setTitle("HardInputMethod");
mWindow = this.getWindow();
LayoutParams lp = mWindow.getAttributes();
lp.gravity = Gravity.LEFT|Gravity.TOP;
lp.x = 0;
lp.y = 0;
mWindow.setType(WindowManager.LayoutParams.TYPE_PHONE);
//初始化window的flag
mWindow.setFlags(flags, flagsNask);
} @Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
//检測到用户触摸了bridge window外的区域,那么焦点view可能要发生
//变化了,输入法的InputConnection须要更新了。所以在此临时取消自己
//的focus
if (DEBUG) Log.d(TAG, "release focus");
releaseFocus();
}
return super.onTouchEvent(event);
} @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DEBUG) Log.d(TAG, "onKeyDown" + keyCode);
//将事件传递给输入法
mAttachedInputMethod.onKeyDown(keyCode, event);
return super.onKeyDown(keyCode, event);
} protected void releaseFocus() {
// TODO Auto-generated method stub
//将自己配置成不可获取焦点来让自己失去焦点
mWindow.setFlags(flags_nofocus, flagsNask);
mHandler.removeCallbacks(mFocusRunnable);
//1s钟后。让自己又一次获取焦点
mHandler.postDelayed(mFocusRunnable, 1000);
} Runnable mFocusRunnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mWindow.setFlags(flags, flagsNask);
}
}; Point mDownPosition = new Point();
public void onDown(int x, int y) {
// TODO Auto-generated method stub
int[] loc = new int[2];
mRootView.getLocationOnScreen(loc);
mDownPosition.x = loc[0];
mDownPosition.y = loc[1] - 50;
if (DEBUG) Log.d(TAG, "on down position x:" + loc[0] + " y:" + loc[1]);
} public void onMove(int offsetX, int offsetY) {
// TODO Auto-generated method stub
updatePositioin(mDownPosition.x + offsetX, mDownPosition.y + offsetY);
} private void updatePositioin(int x, int y) {
LayoutParams lp = mWindow.getAttributes();
lp.x = x;
lp.y = y;
mWindow.setAttributes(lp);
}
}

完美解决方式


上面的解决方式是直接在输入法程序内部改动达到实现外接键盘输入中文。属于应用程范畴。可是仍有一些问题,而这些问题在程序端是没法解决的。

那该怎么完美解决呢。Andorid后来的版本号已经攻克了这个。是怎样解决的?

即全部的按键事件先发送给程序。然后程序端的代码会先将key发送给输入法,即让输入法有一个翻译转换过程的机会,然后输入法再将转化过的key或者字符发送回程序,也就是说key事件绕了一圈。最后再让程序端处理。

附录


近期工作比較忙。代码还没有整理好,等整理好后,我会将源代码发出来。大家能够一起学习。

/********************************

* 本文来自博客  “爱踢门”

* 转载请标明出处:http://blog.csdn.net/itleaks

******************************************/

Android输入法扩展之外接键盘中文输入的更多相关文章

  1. Android输入法扩展之远程输入法

    近年来,互联网电视開始火热,乐视TV,小米TV,近期爱奇艺也在大肆的招人做爱奇艺电视.当然还有更被关注的苹果电视.事实上,这个趋势非常正常,也非常合理,传统单纯的接收电视节目的电视已经太传统了.是该被 ...

  2. iOS 模拟器键盘弹出以及中文输入

    1.虚拟键盘的弹出与收起切换: 快捷键:command+shift+K 2.中文输入: Xcode 菜单项 --> Product --> Scheme --> Edit Schem ...

  3. win10里如何在中文输入法里添加美国键盘

    在控制面板打开“时钟.语言和区域”设置界面,选中“语言”设置   “语言”设置里点击“添加语言”   在添加语言设置里选择“英语”,并点击“打开”按钮,在“区域变量”设置页面里选择“英语(美国)” , ...

  4. android开发(45) 自定义软键盘(输入法)

    概述 在项目开发中遇到一个需求,”只要数字键盘的输入,仅仅有大写字母的输入,某些输入法总是会提示更新,弹出广告等“,使得我们需要自定义输入. 关联到的知识 KeyboardView      一个视图 ...

  5. win10里如何在中文输入法里添加美式键盘

    在控制面板打开“时钟.语言和区域”设置界面,选中“语言”设置  “语言”设置里点击“添加语言” 在添加语言设置里选择“英语”,并点击“打开”按钮,在“区域变量”设置页面里选择“英语(美国)” ,并点击 ...

  6. [转]NME Android目标中文输入问题完美解决!

    最近研究了一下haxe,发现蛮牛逼的,转几篇知识帖 haXe开发笔记:中文问题的小结 * .hx源文件中如果包含中文,要保存成UTF-8编码才能够正确被haXe编译器解析,是否包含BOM(Byte O ...

  7. Android 平板中 自己定义键盘(popuwindow) 居于屏幕左下方 仿微信的password输入界面

          之前博客中,介绍过使用谷歌提供的键盘的一些api,能够非常好地自己定义键盘,參考我之前的博客链接:android 自己定义键盘 ,这个有一个局限性,仅仅能占满屏幕,无法做到仅仅能占一部分的 ...

  8. intellij idea 12、13 win8 下 中文输入覆盖的问题(搜狗输入法或者其他输入法)

    最近升级到idea12,发现中文输入存在问题,输入中文的时候会出现空格,并且覆盖后面的字符,这个问题让我很郁闷. 假设idea的安装位置为:D:\Program Files\JetBrains\Int ...

  9. Android输入法框架系统(下)

    程序焦点获取事件导致输入法显示 从上面可以知道程序获得焦点时,程序端会先间接的调用IMMS的startInput将焦点View绑定到输入法,然后会调用IMMS的windowGainFocus函数,这个 ...

随机推荐

  1. meanShift算法介绍

    meanShift,均值漂移,在聚类.图像平滑.切割.跟踪等方面有着广泛的应用.meanShift这个概念最早是由Fukunage在1975年提出的,其最初的含义正如其名:偏移的均值向量:但随着理论的 ...

  2. c语言,结构体里面的函数

    以linux-3.2内核代码为例,结构体里面的函数的用法: 例,在某驱动文件中,定义了一个平台设备驱动: static struct platform_driver s3c24xx_led_drive ...

  3. 基于visual Studio2013解决面试题之1109全排列

     题目

  4. bottle-session 0.2 : Python Package Index

    bottle-session 0.2 : Python Package Index bottle-session 0.2 Download bottle-session-0.2.tar.gz Redi ...

  5. CorePlot学习零---安装

    刚開始接触CorePlot时,网上搜到非常多相关文章,解说怎样安装这个第三方库,到眼下阶段该库的版本号已经到了1.5了,可是在github上你能够看到他的安装方法,只是为啥就没有codpod来安装呢? ...

  6. Linux设备驱动中的ioctl

    memdev.h #ifndef _MEMDEV_H #define _MEMDEV_H #define MEM_MAGIC 'm' #define MEM_RESTART _IO(MEM_MAGIC ...

  7. FMX对象释放

    今天盒子中有朋友遇到对象释放的问题,原文在这里,他的实现大意是建立一个TmyLayout = class(TLayout),然后在这个类中画线,Form对象调用实例化这个类来画线,然后释放掉这个对象, ...

  8. HTML语言简单回顾

    简单复习一下html语言. html的基本结构如下: <html> <head> <title></title> </head> <b ...

  9. android设置背景图片透明

    设置Activiyt为透明可以在Activity中引用系统透明主题android:theme="@android:style/Theme.Translucent" 设置背景图片透明 ...

  10. c# 文件/文件夹操作

    1.判断文件夹是否存在并创建 if (!Directory.Exists(tempFolderName)) { Directory.CreateDirectory(tempFolderName); }