android --自定义数字软键盘的设计与实现(1)
相信很多情况下我们看到过一些数字的输入,弹出来的并不是系统自带的键盘。这就是自定义的软键盘,软键盘的一个好处就是简单,操作方便。如何实现一个自定义的软键盘呢??其实这个过程是比较简单的,只要把几个关键的原理搞明白了,你就会发现真的很简单,很方便!看一下效果图:
这篇博客主要介绍一下实现的相关原理,下一节就会把具体实现的步骤和大家分享一下!
实现软键盘主要用到了系统的两个类Keyboard和KeyboardView:
Keyboard类源码的介绍是:Listener for virtual keyboard events.即用于监听虚拟键盘。
至于Keyboard类的映射机制,这里就不需要说了,本人感觉也没必要了解的太深,这里要介绍一些他,在接下来需要的几个重要的内容。我们看一下他的源码:
package com.android.inputmethod.keyboard;
import android.util.Log;
import android.util.SparseArray;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.CollectionUtils;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
* consists of rows of keys.
*
The layout file for a keyboard contains XML that looks like the following snippet:
*
* <Keyboard
*latin:keyWidth="%10p"
*latin:keyHeight="50px"
*latin:horizontalGap="2px"
*latin:verticalGap="2px" >
* <Row latin:keyWidth="32px" >
*<Key latin:keyLabel="A" />
*...
* </Row>
* ...
* </Keyboard>
*
*/
public class Keyboard {
private static final String TAG = Keyboard.class.getSimpleName();
/** Some common keys code. Must be positive.
* These should be aligned with values/keycodes.xml
*/
public static final int CODE_ENTER = '\n';
public static final int CODE_TAB = '\t';
public static final int CODE_SPACE = ' ';
public static final int CODE_PERIOD = '.';
public static final int CODE_DASH = '-';
public static final int CODE_SINGLE_QUOTE = '\'';
public static final int CODE_DOUBLE_QUOTE = '"';
public static final int CODE_QUESTION_MARK = '?';
public static final int CODE_EXCLAMATION_MARK = '!';
// TODO: Check how this should work for right-to-left languages. It seems to stand
// that for rtl languages, a closing parenthesis is a left parenthesis. Is this
// managed by the font? Or is it a different char?
public static final int CODE_CLOSING_PARENTHESIS = ')';
public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
public static final int CODE_CLOSING_CURLY_BRACKET = '}';
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
/** Special keys code. Must be negative.
* These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
* KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
*/
public static final int CODE_SHIFT = -1;
public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
public static final int CODE_OUTPUT_TEXT = -3;
public static final int CODE_DELETE = -4;
public static final int CODE_SETTINGS = -5;
public static final int CODE_SHORTCUT = -6;
public static final int CODE_ACTION_ENTER = -7;
public static final int CODE_ACTION_NEXT = -8;
public static final int CODE_ACTION_PREVIOUS = -9;
public static final int CODE_LANGUAGE_SWITCH = -10;
public static final int CODE_RESEARCH = -11;
// Code value representing the code is not specified.
public static final int CODE_UNSPECIFIED = -12;
public final KeyboardId mId;
public final int mThemeId;
/** Total height of the keyboard, including the padding and keys */
public final int mOccupiedHeight;
/** Total width of the keyboard, including the padding and keys */
public final int mOccupiedWidth;
/** The padding above the keyboard */
public final int mTopPadding;
/** Default gap between rows */
public final int mVerticalGap;
/** Per keyboard key visual parameters */
public final KeyVisualAttributes mKeyVisualAttributes;
public final int mMostCommonKeyHeight;
public final int mMostCommonKeyWidth;
/** More keys keyboard template */
public final int mMoreKeysTemplate;
/** Maximum column for more keys keyboard */
public final int mMaxMoreKeysKeyboardColumn;
/** Array of keys and icons in this keyboard */
public final Key[] mKeys;
public final Key[] mShiftKeys;
public final Key[] mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet;
private final SparseArray mKeyCache = CollectionUtils.newSparseArray();
private final ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled;
public Keyboard(final KeyboardParams params) {
/// M: @{
ProximityInfo tmp;
/// @}
mId = params.mId;
mThemeId = params.mThemeId;
mOccupiedHeight = params.mOccupiedHeight;
mOccupiedWidth = params.mOccupiedWidth;
mMostCommonKeyHeight = params.mMostCommonKeyHeight;
mMostCommonKeyWidth = params.mMostCommonKeyWidth;
mMoreKeysTemplate = params.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
mKeyVisualAttributes = params.mKeyVisualAttributes;
mTopPadding = params.mTopPadding;
mVerticalGap = params.mVerticalGap;
mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]);
mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]);
mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray(
new Key[params.mAltCodeKeysWhileTyping.size()]);
mIconsSet = params.mIconsSet;
/// M: Modified by MTK to avoid OOME thrown to DVM on LCA
// since there is an int array sized about 32KB allocated
// within ProximityInfo constructor. @{
try {
tmp = new ProximityInfo(params.mId.mLocale.toString(),
params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
} catch (OutOfMemoryError e) {
Log.w(TAG, "OutOfMemoryError at ProximityInfo");
/// M: It`s dangerous here, since this approach will generate
// a proximity useless, this will cause no responce on VK.
// TODO: It`s better to unbind LatinIME here or provide another
// keyboard here.
tmp = ProximityInfo.createDummyProximityInfo();
}
mProximityInfo = tmp;
/// @}
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
}
public boolean hasProximityCharsCorrection(final int code) {
if (!mProximityCharsCorrectionEnabled) {
return false;
}
// Note: The native code has the main keyboard layout only at this moment.
// TODO: Figure out how to handle proximity characters information of all layouts.
final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (
mId.mElementId == KeyboardId.ELEMENT_ALPHABET
|| mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
}
public ProximityInfo getProximityInfo() {
return mProximityInfo;
}
public Key getKey(final int code) {
if (code == CODE_UNSPECIFIED) {
return null;
}
synchronized (mKeyCache) {
final int index = mKeyCache.indexOfKey(code);
if (index >= 0) {
return mKeyCache.valueAt(index);
}
for (final Key key : mKeys) {
if (key.mCode == code) {
mKeyCache.put(code, key);
return key;
}
}
mKeyCache.put(code, null);
return null;
}
}
public boolean hasKey(final Key aKey) {
if (mKeyCache.indexOfValue(aKey) >= 0) {
return true;
}
for (final Key key : mKeys) {
if (key == aKey) {
mKeyCache.put(key.mCode, key);
return true;
}
}
return false;
}
public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
}
@Override
public String toString() {
return mId.toString();
}
/**
* Returns the array of the keys that are closest to the given point.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return the array of the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
public Key[] getNearestKeys(final int x, final int y) {
// Avoid dead pixels at edges of the keyboard
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
public static String printableCode(final int code) {
switch (code) {
case CODE_SHIFT: return "shift";
case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
case CODE_OUTPUT_TEXT: return "text";
case CODE_DELETE: return "delete";
case CODE_SETTINGS: return "settings";
case CODE_SHORTCUT: return "shortcut";
case CODE_ACTION_ENTER: return "actionEnter";
case CODE_ACTION_NEXT: return "actionNext";
case CODE_ACTION_PREVIOUS: return "actionPrevious";
case CODE_LANGUAGE_SWITCH: return "languageSwitch";
case CODE_UNSPECIFIED: return "unspec";
case CODE_TAB: return "tab";
case CODE_ENTER: return "enter";
default:
if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);
if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
if (code < 0x100) return String.format("'%c'", code);
return String.format("'\\u%04x'", code);
}
}
}
我们在设置每一个按键的code时,就是根据keyboard类中定义的一些属性,比如回退,删除,清空等都是固定。
知道了
public static final int CODE_SHIFT = -1;
public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
public static final int CODE_OUTPUT_TEXT = -3;
public static final int CODE_DELETE = -4;
public static final int CODE_SETTINGS = -5;
public static final int CODE_SHORTCUT = -6;
public static final int CODE_ACTION_ENTER = -7;
public static final int CODE_ACTION_NEXT = -8;
public static final int CODE_ACTION_PREVIOUS = -9;
public static final int CODE_LANGUAGE_SWITCH = -10;
public static final int CODE_RESEARCH = -11;
// Code value representing the code is not specified.
public static final int CODE_UNSPECIFIED = -12;
我们就不会有太多的疑惑了!也是说,我们自定义的每一个按键都将会有一个codes值,比如回退我们就写成:
在监听就是:if (primaryCode == Keyboard.KEYCODE_DELETE){}.这就表示,监听回退事件了!
KeyboardView类源码的介绍是:A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key presses and touch movements.即它处理绘制钥匙和检测按键和触摸动作。这个类的代码比较多,我们就不贴出来,它里面有很多方法,在我们自定义的软键盘很多属性,就需要我们用这个类来设置比如:
keyboardView = (KeyboardView) act.findViewById(R.id.keyboard_view);
keyboardView.setKeyboard(k);
keyboardView.setEnabled(true);
keyboardView.setPreviewEnabled(true);
keyboardView.setVisibility(View.VISIBLE);
keyboardView.setOnKeyboardActionListener(listener);
了解一些源码,就可以是我们知道我们为什么要这样写,为什么要这样做了!
我们还需要建一个xml文件,来布局我们的视图,一般是在res文件夹中建一个名为xml的文件夹,在里面新建立一个xml布局文件。
每一个按键的属性主要包括android:codes=" " android:keyLabel=" "。activity就是根据codes的值来监听的。一些可以自定义设置,一些需要是keyboard中设置好的,要保持一致。
好了,今天就先说到这里,下次再继续说建立一个完整的项目实例的过程!