本文主要介绍openwnn对Keyboard和KeyboardView的处理。
这一部分主要涉及三个类:InputViewManager.java,DefaultSoftKeyboard.java,DefaultSoftKeyboardJAJP.java。其中InputViewManager是与键盘相关的对外接口,DefaultSoftKeyboard是通用类,DefaultSoftKeyboardJAJP是日文定制类。
1、InputViewManager
第一步我们先来看看InputViewManager。这个接口类代码很简单:
-
-
-
-
-
- public interface InputViewManager {
-
-
-
-
-
-
-
-
-
- public View initView(OpenWnn parent, int width, int height);
-
-
-
-
-
-
- public View getCurrentView();
-
-
-
-
-
-
- public void onUpdateState(OpenWnn parent);
-
-
-
-
-
-
-
- public void setPreferences(SharedPreferences pref, EditorInfo editor);
-
-
-
-
- public void closing();
- }
从这个接口文件中,我们可以看出在输入法处理中,对于键盘部分需要涉及的操作并不是很多。
2、配置项
这里我们先从简单的部分开始研究。第一个是setPreferences,这只配置项。这一步的工作是读取与键盘部分有关的配置项,在生成键盘(改变键盘)时进行设置。代码中设计到的配置项很减少,只有:震动、声音、是否自动切换大写。在DefaultSoftKeyboard.java中只设置了震动和声音,在DefaultSoftKeyboardJAJP.java添加了是否自动切换为大写。具体大家可以看代码,对于是否自动切换大写,从代码上看,我猜测,有些输入框默认是输入大写的。(这一部分我想不到例子,谁有例子可以share一下)
3、KeyboardView
在InputViewManager中有initView这个函数,实际上使用来生成KeyboardView的。其源码如下:
在DefaultSoftKeyboard.java中:
-
- public View initView(OpenWnn parent, int width, int height) {
- mWnn = parent;
- mDisplayMode = (width == 320)? PORTRAIT : LANDSCAPE;
-
-
-
-
-
-
- createKeyboards(parent);
-
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(parent);
- String skin = pref.getString("keyboard_skin",
- mWnn.getResources().getString(R.string.keyboard_skin_id_default));
- Log.d("OpenWnn", "keyboard_skin="+skin);
- int id = parent.getResources().getIdentifier(skin, "layout", "jp.co.omronsoft.openwnn");
-
- mKeyboardView = (KeyboardView) mWnn.getLayoutInflater().inflate(id, null);
- mKeyboardView.setOnKeyboardActionListener(this);
- mCurrentKeyboard = null;
-
- mMainView = (ViewGroup) parent.getLayoutInflater().inflate(R.layout.keyboard_default_main, null);
- mSubView = (ViewGroup) parent.getLayoutInflater().inflate(R.layout.keyboard_default_sub, null);
- if (mDisplayMode == LANDSCAPE && !mHardKeyboardHidden) {
- mMainView.addView(mSubView);
- }
- if (mKeyboardView != null) {
- mMainView.addView(mKeyboardView);
- }
-
- return mMainView;
- }
-
其中我们可以看到,程序先通过layout文件创建KeyboardView,然后通过将其封装在一个mMainView的view变量中。这里很重要的一段代码是,他会去配置项中读取”skin“这一配置项,并通过该配置项去读取对应的layout文件。这一步是动态变换皮肤的关键,也就是说你可以在配置项中选择不同的皮肤,程序会根据你的选择来生成不同的皮肤。
另外,从SoftKeyboard项目中我们知道,KeyboardView实际上是装着一个Keyboard。但在这一段代码中只有生成Keyboard,并未将Keyboard封装到KeyboardView里面,因为当前键盘变量mCurrentKeyboard是空的。这里我们猜测在继承类中会做封装Keyboard的操作。我们看DefaultSoftKeyboardJAJP.java的代码:
-
- @Override public View initView(OpenWnn parent, int width, int height) {
-
- View view = super.initView(parent, width, height);
- changeKeyboard(mKeyboard[mCurrentLanguage][mDisplayMode][mCurrentKeyboardType][mShiftOn][mCurrentKeyMode][0]);
-
- return view;
- }
这里changeKeyboard是父类DefaultSoftKeyboard中的函数,其代码为:
-
-
-
-
-
-
- protected boolean changeKeyboard(Keyboard keyboard) {
-
- if (keyboard == null) {
- return false;
- }
- if (mCurrentKeyboard != keyboard) {
- mKeyboardView.setKeyboard(keyboard);
- mKeyboardView.setShifted((mShiftOn == 0) ? false : true);
- mCurrentKeyboard = keyboard;
- return true;
- } else {
- mKeyboardView.setShifted((mShiftOn == 0) ? false : true);
- return false;
- }
- }
这里我们可以看到mKeyboardView.setKeyboard(keyboard)这一句,这就验证了我们对KeyboardView实际上是装着一个Keyboard的猜测。
4、Keyboard
这里的Keyboard异常复杂,以至于需要一个5维数组来维护。
-
-
-
-
-
- protected Keyboard[][][][][][] mKeyboard;
在DefaultSoftKeyboard.java中有许多函数是在做键盘切换的。另外,这个数组中的每个元素都是一个Keyboard,他们在DefaultSoftKeyboardJAJP.java类中的createKeyboards函数中创建,其中又调用了createKeyboardsPortrait和createKeyboardsLandscape两个函数来做具体的创建工作。具体而言是每个键盘对应一个配置文件,这些文件存放在xml文件夹下面。
键盘的处理还是比复杂,因为根据不同的输入框,需要显示不同的键盘;同时用户可以选择不同的输入法模式,此时又需要显示不同的键盘。这一点从onUpdateState 函数,以及onKey函数等可以看出。
另外,有些手机不是纯触摸屏的,也就是带有键盘的手机。对于这些手机是不会显示软键盘的(我猜测),对此需要对硬件盘信息进行设置。其中setHardKeyboardHidden函数就是用于做这些事情的。
5、onUpdateState
从函数名我们可以看出这一函数的目的是用于更新状态,主要是用户更新键盘状态(或者是切换键盘)。从DefaultSoftKeyboard类中代码可以看出:
-
- public void onUpdateState(OpenWnn parent) {
- try {
- if (parent.mComposingText.size(1) == 0) {
- if (!mNoInput) {
-
- mNoInput = true;
- Keyboard newKeyboard = getKeyboardInputed(false);
- if (mCurrentKeyboard != newKeyboard) {
- changeKeyboard(newKeyboard);
- }
- }
- } else {
- if (mNoInput) {
-
- mNoInput = false;
- Keyboard newKeyboard = getKeyboardInputed(true);
- if (mCurrentKeyboard != newKeyboard) {
- changeKeyboard(newKeyboard);
- }
- }
- }
- } catch (Exception ex) {
- }
- }
其中mNoInput定义为:
-
-
-
-
-
- protected boolean mNoInput = true;
这一段代码主要是根据当前的输入状态,来更新键盘的。程序判断当前输入串(这里指输入时,带有下划线的输入串)是否为空。若输入串为空,判断mNoInput是否为有输入串(false),若是,则mNoInput改为true,同时键盘改为没有输入串的状态;若输入串不为空,此时若未没有输入串的状态,则要改为有输入串的状态,并相应修改键盘。
而DefaultSoftKeyboardJAJP中的这段代码则较为简单:
-
- @Override public void onUpdateState(OpenWnn parent) {
- super.onUpdateState(parent);
- setShiftByEditorInfo();
- }
-
-
-
- private void setShiftByEditorInfo() {
- if (mEnableAutoCaps && (mCurrentKeyMode == KEYMODE_JA_HALF_ALPHABET)) {
- int shift = getShiftKeyState(mWnn.getCurrentInputEditorInfo());
-
- mShiftOn = shift;
- changeKeyboard(getShiftChangeKeyboard(shift));
- }
- }
这一部分代码主要是根据当前输入框的特点相应设置对应的键盘。
6、输入方式(直接上屏或者参与变换)
另外,在使用输入法输入时,通常会有两种方式,一种是参与变换,一种是直接上屏。参与变换是指,你输入的内容与你需要选择的内容不是一致的,而是通过一系列复杂的变换得到的,比如你输入”kawai“得到”可愛“,就是通过变换而来的;而你在输入字符或者数字时,通常是直接上屏的。前者需要显示CandidateView的,而后者不要。
这一点可能影响的地方包括:不同的输入框(如密码输入框)和不同的输入模式(或者说是输入内容),比如输入数字和字符时,通常就是直接上屏的。
对于这一部分的技术处理,我们从DefaultSoftKeyboardJAJP类中的changeKeyMode函数可以看出一点端倪。由于该函数代码有点长,就不展现出来了。我们看其中几行代码:
- case KEYMODE_JA_HALF_ALPHABET:
- if (USE_ENGLISH_PREDICT) {
- mInputType = INPUT_TYPE_TOGGLE;
- mode = OpenWnnEvent.Mode.NO_LV1_CONV;
- } else {
- mInputType = INPUT_TYPE_TOGGLE;
- mode = OpenWnnEvent.Mode.DIRECT;
- }
- break;
如果当前的模式是半角字母输入,则若使用英文预测,则使用的是参与变换的输入方式,若不是用英文预测,则是直接上屏的输入方式。
另外changeKeyMode函数的前两行是
- int targetMode = keyMode;
- commitText();
这是调用了commitText函数,该函数的主要功能是上屏,也就是将输入的内容输出到输入框(上屏后的内容没有下划线),在切换输入模式时,通常需要先将当前输入的内容上屏。
另外,需要提一下,在openwnn日文输入法中,有一个功能是切换输入模式,如下图
左下角那个按钮,你按一下他会不断变换,从假名输入、英文输入、数字输入循环切换。
其实现方式如下:
-
- private static final int[] JP_MODE_CYCLE_TABLE = {
- KEYMODE_JA_FULL_HIRAGANA, KEYMODE_JA_HALF_ALPHABET, KEYMODE_JA_HALF_NUMBER
- };
-
-
-
- private void nextKeyMode() {
-
- boolean found = false;
- int index;
- for (index = 0; index < JP_MODE_CYCLE_TABLE.length; index++) {
- if (JP_MODE_CYCLE_TABLE[index] == mCurrentKeyMode) {
- found = true;
- break;
- }
- }
-
- if (!found) {
-
- setDefaultKeyboard();
- } else {
-
- index++;
- if (JP_MODE_CYCLE_TABLE.length <= index) {
- index = 0;
- }
- changeKeyMode(JP_MODE_CYCLE_TABLE[index]);
- }
- }
这里从程序中就很容易看出是一个循环切换的过程。同时这个函数,在类似如下场合调用:
-
- @Override public void onKey(int primaryCode, int[] keyCodes) {
-
- switch (primaryCode) {
- case KEYCODE_JP12_TOGGLE_MODE:
- case KEYCODE_QWERTY_TOGGLE_MODE:
- nextKeyMode();
- break;
7、输入变换
7.1 12key键盘
在12key的键盘中,是无法显示所有输入内容的。于是你按一个键,可能包含多个信息。比如诺基亚的12键键盘:
在这种键盘中,你要输入c,则要按三下”2“键才可以。
同样的,在日文输入法中,如下键盘(软键盘),你是无法显示所有输入内容的,因此,你可能也像用诺基亚键盘一样,需要按多次才可以输入一个内容。
比如,在如上键盘中,你按”か“键,则按1下、2下、3下、4下,5下,6下……显示的内容分别是:"か","き", "く", "け", "こ","か",……。(注意需要在假名输入模式下)
这里的程序实现是比较巧妙,其中涉及的代码如下:
-
- private static final String[][] JP_FULL_HIRAGANA_CYCLE_TABLE = {
- {"\u3042", "\u3044", "\u3046", "\u3048", "\u304a", "\u3041", "\u3043", "\u3045", "\u3047", "\u3049"},
- {"\u304b", "\u304d", "\u304f", "\u3051", "\u3053"},
- {"\u3055", "\u3057", "\u3059", "\u305b", "\u305d"},
- {"\u305f", "\u3061", "\u3064", "\u3066", "\u3068", "\u3063"},
- {"\u306a", "\u306b", "\u306c", "\u306d", "\u306e"},
- {"\u306f", "\u3072", "\u3075", "\u3078", "\u307b"},
- {"\u307e", "\u307f", "\u3080", "\u3081", "\u3082"},
- {"\u3084", "\u3086", "\u3088", "\u3083", "\u3085", "\u3087"},
- {"\u3089", "\u308a", "\u308b", "\u308c", "\u308d"},
- {"\u308f", "\u3092", "\u3093", "\u308e", "\u30fc"},
- {"\u3001", "\u3002", "\uff1f", "\uff01", "\u30fb", "\u3000"},
- };
这是一个循环变换table,其内容用utf-8码显示,对此你可能有点迷糊,但是我把他转为日文,你就懂了:
- {"あ", "い", "う", "え", "お", "ぁ", "ぃ", "ぅ", "ぇ", "ぉ"},
- {"か", "き", "く", "け", "こ"},
- {"さ", "し", "す", "せ", "そ"},
- {"た", "ち", "つ", "て", "と", "っ"},
- {"な", "に", "ぬ", "ね", "の"},
- {"は", "ひ", "ふ", "へ", "ほ"},
- {"ま", "み", "む", "め", "も"},
- {"や", "ゆ", "よ", "ゃ", "ゅ", "ょ"},
- {"ら", "り", "る", "れ", "ろ"},
- {"わ", "を", "ん", "ゎ", "ー"},
- {"、", "。", "?", "!", "・", " "},
看到了吧,第二行就是我们刚才按
”
か
“键那个例子中显示的内容。因此其实现我们估计也是很简单的,就是不断的读取这一行的内容。其程序如下:
在@Override public void onKey(int primaryCode, int[] keyCodes)函数中:
- case KEYCODE_JP12_SHARP:
-
- if (mInputType == INPUT_TYPE_INSTANT) {
-
- commitText();
- mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.INPUT_CHAR,
- mCurrentInstantTable[getTableIndex(primaryCode)]));
- } else {
- if ((mPrevInputKeyCode != primaryCode)) {
- if ((mCurrentKeyMode == KEYMODE_JA_HALF_ALPHABET)
- && (primaryCode == KEYCODE_JP12_SHARP)) {
-
- commitText();
- }
- }
-
-
- String[][] cycleTable = getCycleTable();
- if (cycleTable == null) {
- Log.e("OpenWnn", "not founds cycle table");
- } else {
- int index = getTableIndex(primaryCode);
- mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.TOGGLE_CHAR, cycleTable[index]));
- mCurrentCycleTable = cycleTable[index];
- }
- mPrevInputKeyCode = primaryCode;
- }
- break;
其中 getCycleTable()定义如下:
-
-
-
-
-
- private String[][] getCycleTable() {
- String[][] cycleTable = null;
- switch (mCurrentKeyMode) {
- case KEYMODE_JA_FULL_HIRAGANA:
- cycleTable = JP_FULL_HIRAGANA_CYCLE_TABLE;
- break;
-
- case KEYMODE_JA_FULL_KATAKANA:
- cycleTable = JP_FULL_KATAKANA_CYCLE_TABLE;
- break;
-
- case KEYMODE_JA_FULL_ALPHABET:
- cycleTable = JP_FULL_ALPHABET_CYCLE_TABLE;
- break;
-
- case KEYMODE_JA_FULL_NUMBER:
- case KEYMODE_JA_HALF_NUMBER:
-
- break;
-
- case KEYMODE_JA_HALF_ALPHABET:
- cycleTable = JP_HALF_ALPHABET_CYCLE_TABLE;
- break;
-
- case KEYMODE_JA_HALF_KATAKANA:
- cycleTable = JP_HALF_KATAKANA_CYCLE_TABLE;
- break;
-
- default:
- break;
- }
- return cycleTable;
- }
剩下的就不用解释了吧,看代码就懂了。
7.2 输入变换
大家看了上面的解释,估计会对源码中的后缀为REPLACE_TABLE的变量有了一定的猜测。这些变量是为了输入时变换使用的,大家有没有注意到假名输入和英文输入时,键盘上会有一个“大—小”的转换键。这个键的功能就是基于后缀为REPLACE_TABLE的变量实现的。
在使用是日文输入时,你输入“あ”,按“大—小”变换键后会变为“ぁ”。于是我们来看一下,平假名的REPLACE_TABLE,该变量名为JP_FULL_HIRAGANA_REPLACE_TABLE,该变量的内容为utf-8码,转为文字显示如下:
- put("あ", "ぁ") put("い", "ぃ") put("う", "ぅ") put("え", "ぇ") put("お", "ぉ")
- put("ぁ", "あ") put("ぃ", "い") put("ぅ", "ヴ") put("ぇ", "え") put("ぉ", "お")
- put("か", "が") put("き", "ぎ") put("く", "ぐ") put("け", "げ") put("こ", "ご")
- put("が", "か") put("ぎ", "き") put("ぐ", "く") put("げ", "け") put("ご", "こ")
- put("さ", "ざ") put("し", "じ") put("す", "ず") put("せ", "ぜ") put("そ", "ぞ")
- put("ざ", "さ") put("じ", "し") put("ず", "す") put("ぜ", "せ") put("ぞ", "そ")
- put("た", "だ") put("ち", "ぢ") put("つ", "っ") put("て", "で") put("と", "ど")
- put("だ", "た") put("ぢ", "ち") put("っ", "づ") put("で", "て") put("ど", "と")
- put("づ", "つ") put("ヴ", "う")
- put("は", "ば") put("ひ", "び") put("ふ", "ぶ") put("へ", "べ") put("ほ", "ぼ")
- put("ば", "ぱ") put("び", "ぴ") put("ぶ", "ぷ") put("べ", "ぺ") put("ぼ", "ぽ")
- put("ぱ", "は") put("ぴ", "ひ") put("ぷ", "ふ") put("ぺ", "へ") put("ぽ", "ほ")
- put("や", "ゃ") put("ゆ", "ゅ") put("よ", "ょ")
- put("ゃ", "や") put("ゅ", "ゆ") put("ょ", "よ")
- put("わ", "ゎ")
- put("ゎ", "わ")
- put("゛", "゜")
- put("゜", "゛")
看到这个表,大家现在知道是为什么了吧。
8、其他
到这里,我们就将Keyboard和KeyboardView这一部分介绍完成了。相信大家已经对openwnn中这一部分有了必要的了解。
1)具体的键盘定义,也就是如何从一个xml文件生存一个键盘 但是在解析源码中有两部分内容我们并没有说明
2)按键处理
3)DefaultSoftKeyboard.java,DefaultSoftKeyboardJAJP.java文件中关于事件部分代码并没有解释
对于1)后续会不上,对于2)和3)我感觉放在其他地方介绍会好一点,因此会在后续的文章中介绍2)和3)。至于1)后续有时间会补上。