android输入法02:openwnn源码解析02—Keyboard和KeyboardView

本文主要介绍openwnn对Keyboard和KeyboardView的处理。

这一部分主要涉及三个类:InputViewManager.java,DefaultSoftKeyboard.java,DefaultSoftKeyboardJAJP.java。其中InputViewManager是与键盘相关的对外接口,DefaultSoftKeyboard是通用类,DefaultSoftKeyboardJAJP是日文定制类。

1、InputViewManager

第一步我们先来看看InputViewManager。这个接口类代码很简单:

/** * The interface of input view manager used by OpenWnn. * * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD. All Rights Reserved. */ public interface InputViewManager { /** * Initialize the input view. * * @param parent The OpenWnn object * @param width The width of the display * @param height The height of the display * * @return The input view created in the initialize process; {@code null} if cannot create a input view. */ public View initView(OpenWnn parent, int width, int height); /** * Get the input view being used currently. * * @return The input view; {@code null} if no input view is used currently. */ public View getCurrentView(); /** * Notification of updating parent's state. * * @param parent The OpenWnn object using this manager */ public void onUpdateState(OpenWnn parent); /** * Reflect the preferences in the input view. * * @param pref The preferences * @param editor The information about the editor */ public void setPreferences(SharedPreferences pref, EditorInfo editor); /** * Close the input view. */ public void closing(); } 从这个接口文件中,我们可以看出在输入法处理中,对于键盘部分需要涉及的操作并不是很多。

2、配置项

这里我们先从简单的部分开始研究。第一个是setPreferences,这只配置项。这一步的工作是读取与键盘部分有关的配置项,在生成键盘(改变键盘)时进行设置。代码中设计到的配置项很减少,只有:震动、声音、是否自动切换大写。在DefaultSoftKeyboard.java中只设置了震动和声音,在DefaultSoftKeyboardJAJP.java添加了是否自动切换为大写。具体大家可以看代码,对于是否自动切换大写,从代码上看,我猜测,有些输入框默认是输入大写的。(这一部分我想不到例子,谁有例子可以share一下)

3、KeyboardView

在InputViewManager中有initView这个函数,实际上使用来生成KeyboardView的。其源码如下:

在DefaultSoftKeyboard.java中:

/** @see jp.co.omronsoft.openwnn.InputViewManager#initView */ public View initView(OpenWnn parent, int width, int height) { mWnn = parent; mDisplayMode = (width == 320)? PORTRAIT : LANDSCAPE; /* * create keyboards & the view. * To re-display the input view when the display mode is changed portrait <-> landscape, * create keyboards every time. */ 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的代码:

/** @see jp.co.omronsoft.openwnn.DefaultSoftKeyboard#initView */ @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中的函数,其代码为:

/** * Change the keyboard. * * @param keyboard The new keyboard * @return {@code true} if the keyboard is changed; {@code false} if not changed. */ 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维数组来维护。

/** * Keyboard surfaces * <br> * Keyboard[language][portrait/landscape][keyboard type][shift off/on][key-mode] */ protected Keyboard[][][][][][] mKeyboard; 在DefaultSoftKeyboard.java中有许多函数是在做键盘切换的。另外,这个数组中的每个元素都是一个Keyboard,他们在DefaultSoftKeyboardJAJP.java类中的createKeyboards函数中创建,其中又调用了createKeyboardsPortrait和createKeyboardsLandscape两个函数来做具体的创建工作。具体而言是每个键盘对应一个配置文件,这些文件存放在xml文件夹下面。

键盘的处理还是比复杂,因为根据不同的输入框,需要显示不同的键盘;同时用户可以选择不同的输入法模式,此时又需要显示不同的键盘。这一点从onUpdateState 函数,以及onKey函数等可以看出。

另外,有些手机不是纯触摸屏的,也就是带有键盘的手机。对于这些手机是不会显示软键盘的(我猜测),对此需要对硬件盘信息进行设置。其中setHardKeyboardHidden函数就是用于做这些事情的。

5、onUpdateState

从函数名我们可以看出这一函数的目的是用于更新状态,主要是用户更新键盘状态(或者是切换键盘)。从DefaultSoftKeyboard类中代码可以看出:

/** @see jp.co.omronsoft.openwnn.InputViewManager#onUpdateState */ public void onUpdateState(OpenWnn parent) { try { if (parent.mComposingText.size(1) == 0) { if (!mNoInput) { /* when the mode changed to "no input" */ mNoInput = true; Keyboard newKeyboard = getKeyboardInputed(false); if (mCurrentKeyboard != newKeyboard) { changeKeyboard(newKeyboard); } } } else { if (mNoInput) { /* when the mode changed to "input some characters" */ mNoInput = false; Keyboard newKeyboard = getKeyboardInputed(true); if (mCurrentKeyboard != newKeyboard) { changeKeyboard(newKeyboard); } } } } catch (Exception ex) { } }其中mNoInput定义为:

/** * Status of the composing text * <br> * {@code true} if there is no composing text. */ protected boolean mNoInput = true;

这一段代码主要是根据当前的输入状态,来更新键盘的。程序判断当前输入串(这里指输入时,带有下划线的输入串)是否为空。若输入串为空,判断mNoInput是否为有输入串(false),若是,则mNoInput改为true,同时键盘改为没有输入串的状态;若输入串不为空,此时若未没有输入串的状态,则要改为有输入串的状态,并相应修改键盘。

而DefaultSoftKeyboardJAJP中的这段代码则较为简单:

/** @see jp.co.omronsoft.openwnn.DefaultSoftKeyboard#onUpdateState */ @Override public void onUpdateState(OpenWnn parent) { super.onUpdateState(parent); setShiftByEditorInfo(); }/** * Set the shift key state from {@link EditorInfo}. */ 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日文输入法中,有一个功能是切换输入模式,如下图


左下角那个“文字”按钮,你按一下他会不断变换,从假名输入、英文输入、数字输入循环切换。

其实现方式如下:

/** Input mode toggle cycle table */ private static final int[] JP_MODE_CYCLE_TABLE = { KEYMODE_JA_FULL_HIRAGANA, KEYMODE_JA_HALF_ALPHABET, KEYMODE_JA_HALF_NUMBER }; /** * Change to the next input mode */ private void nextKeyMode() { /* Search the current mode in the toggle table */ 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) { /* If the current mode not exists, set the default mode */ setDefaultKeyboard(); } else { /* If the current mode exists, set the next input mode */ index++; if (JP_MODE_CYCLE_TABLE.length <= index) { index = 0; } changeKeyMode(JP_MODE_CYCLE_TABLE[index]); } } 这里从程序中就很容易看出是一个循环切换的过程。同时这个函数,在类似如下场合调用:

/** @see jp.co.omronsoft.openwnn.DefaultSoftKeyboard#onKey */ @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下……显示的内容分别是:"か","き", "く", "け", "こ","か",……。(注意需要在假名输入模式下)

这里的程序实现是比较巧妙,其中涉及的代码如下:

/** Toggle cycle table for full-width HIRAGANA */ 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: /* Processing to input by ten key */ if (mInputType == INPUT_TYPE_INSTANT) { /* Send a input character directly if instant input type is selected */ 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)) { /* Commit text by symbol character (',' '.') when alphabet input mode is selected */ commitText(); } } /* Convert the key code to the table index and send the toggle event with the table index */ 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()定义如下:

/** * Get the toggle table for input that is appropriate in current mode. * * @return The toggle table for input */ 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: /* Because these modes belong to direct input group, No toggle table exists */ 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)后续有时间会补上。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值