为了自己学习方便,对Android的部分源码原样粘贴在这里,然后自己编辑,用中文注释了一下。不是原创代码。
/* * Copyright (C) 2008-2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package android.inputmethodservice; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.util.Xml; import android.util.DisplayMetrics; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * class Keyboard:该类的作用就是 加载一个描述键盘的XML文件、储存其中键的属性。 * Loads an XML description of a keyboard and stores the attributes of the keys. * A keyboard consists of rows of keys. * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> * <pre> * <Keyboard * android:keyWidth="%10p" * android:keyHeight="50px" * android:horizontalGap="2px" * android:verticalGap="2px" > * <Row android:keyWidth="32px" > * <Key android:keyLabel="A" /> * ... * </Row> * ... * </Keyboard> * </pre> * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_verticalGap */ public class Keyboard { static final String TAG = "Keyboard"; // Keyboard XML Tags(XML的标签,常量) private static final String TAG_KEYBOARD = "Keyboard"; private static final String TAG_ROW = "Row"; private static final String TAG_KEY = "Key"; public static final int EDGE_LEFT = 0x01; public static final int EDGE_RIGHT = 0x02; public static final int EDGE_TOP = 0x04; public static final int EDGE_BOTTOM = 0x08; public static final int KEYCODE_SHIFT = -1; public static final int KEYCODE_MODE_CHANGE = -2; public static final int KEYCODE_CANCEL = -3; public static final int KEYCODE_DONE = -4; public static final int KEYCODE_DELETE = -5; public static final int KEYCODE_ALT = -6; /** Keyboard label(键的标签,UI上显示的字符,变量) **/ private CharSequence mLabel; /** Horizontal gap default for all rows */ private int mDefaultHorizontalGap; /** Default key width */ private int mDefaultWidth; /** Default key height */ private int mDefaultHeight; /** Default gap between rows */ private int mDefaultVerticalGap; /** Is the keyboard in the shifted state */ private boolean mShifted; /** Key instance for the shift key, if present */ private Key[] mShiftKeys = { null, null }; /** Key index for the shift key, if present */ private int[] mShiftKeyIndices = {-1, -1}; /** Current key width, while loading the keyboard */ private int mKeyWidth; /** Current key height, while loading the keyboard */ private int mKeyHeight; /** Total height of the keyboard, including the padding and keys */ private int mTotalHeight; /** * Total width of the keyboard, including left side gaps and keys, but not any gaps on the * right side. */ private int mTotalWidth; /** List of keys in this keyboard */ private List<Key> mKeys; /** List of modifier keys such as Shift & Alt, if any */ private List<Key> mModifierKeys; /** Width of the screen available to fit the keyboard */ private int mDisplayWidth; /** Height of the screen */ private int mDisplayHeight; /** Keyboard mode, or zero, if none. */ private int mKeyboardMode; // Variables for pre-computing nearest keys. private static final int GRID_WIDTH = 10; private static final int GRID_HEIGHT = 5; private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; private int mCellWidth; private int mCellHeight; private int[][] mGridNeighbors; private int mProximityThreshold; /** Number of key widths from current touch point to search for nearest keys. */ private static float SEARCH_DISTANCE = 1.8f; private ArrayList<Row> rows = new ArrayList<Row>(); /** * class Row :定义每一行键的特征 * Container for keys in the keyboard.(键盘中键的容器) All keys in a row are at the same Y-coordinate. * Some of the key size defaults can be overridden per row from what the {@link Keyboard}defines. * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_verticalGap * @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags * @attr ref android.R.styleable#Keyboard_Row_keyboardMode */ public static class Row { /** Default width of a key in this row. */ public int defaultWidth; /** Default height of a key in this row. */ public int defaultHeight; /** Default horizontal gap between keys in this row. */ public int defaultHorizontalGap; /** Vertical gap following this row. */ public int verticalGap; ArrayList<Key> mKeys = new ArrayList<Key>(); /** * Edge flags for this row of keys. Possible values that can be assigned are * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} */ public int rowEdgeFlags; /** The keyboard mode for this row */ public int mode; private Keyboard parent; public Row(Keyboard parent) { this.parent = parent; } public Row(Resources res, Keyboard parent, XmlResourceParser parser) {//Xml资源解析器 this.parent = parent; TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), com.android.internal.R.styleable.Keyboard); defaultWidth = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth); defaultHeight = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyHeight, parent.mDisplayHeight, parent.mDefaultHeight); defaultHorizontalGap = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap); verticalGap = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_verticalGap, parent.mDisplayHeight, parent.mDefaultVerticalGap); a.recycle(); a = res.obtainAttributes(Xml.asAttributeSet(parser), com.android.internal.R.styleable.Keyboard_Row); rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0); mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode, 0); } } /** * class Key:定义单个键的位置和特征 * Class for describing the position and characteristics of a single key in the keyboard. * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_Key_codes * @attr ref android.R.styleable#Keyboard_Key_keyIcon * @attr ref android.R.styleable#Keyboard_Key_keyLabel * @attr ref android.R.styleable#Keyboard_Key_iconPreview * @attr ref android.R.styleable#Keyboard_Key_isSticky * @attr ref android.R.styleable#Keyboard_Key_isRepeatable * @attr ref android.R.styleable#Keyboard_Key_isModifier * @attr ref android.R.styleable#Keyboard_Key_popupKeyboard * @attr ref android.R.styleable#Keyboard_Key_popupCharacters * @attr ref android.R.styleable#Keyboard_Key_keyOutputText * @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags */ public static class Key { /** * 这个键的所有键值(code)都应该生成,第0个是总重要的。 * All the key codes (unicode or custom code) that this key could generate, * zero'th being the most important. */ public int[] codes; /** Label to display(每个键显示的Label) */ public CharSequence label; /** Icon to display instead of a label. Icon takes precedence(优先) over a label */ public Drawable icon; /** Preview version of the icon, for the preview popup(预览弹出框) */ public Drawable iconPreview; /** Width of the key, not including the gap */ public int width; /** Height of the key, not including the gap */ public int height; /** The horizontal gap before this key */ public int gap; /** Whether this key is sticky(粘滞键), i.e., a toggle key */ public boolean sticky; /** X coordinate of the key in the keyboard layout */ public int x; /** Y coordinate of the key in the keyboard layout */ public int y; /** The current pressed state of this key */ public boolean pressed; /** If this is a sticky key, is it on? 如果是粘滞键,on应该是粘滞状态*/ public boolean on; /** Text to output when pressed. This can be multiple characters, like ".com" */ public CharSequence text; /** Popup characters */ public CharSequence popupCharacters; /** * 标志(标志寄存器)(bit mask 位掩码),明确的指出键的边界,以便检测出超出键边界的触摸事件。 * Flags that specify the anchoring to edges of the keyboard for detecting touch events * that are just out of the boundary of the key. This is a bit mask of * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and * {@link Keyboard#EDGE_BOTTOM}. */ public int edgeFlags;//(0000 1111:表示在按键范围内) /** Whether this is a modifier key(辅助按键,修改其他键的功能), such as Shift or Alt */ public boolean modifier; /** The keyboard that this key belongs to */ private Keyboard keyboard; /** * If this key pops up a mini keyboard, this is the resource id for the XML layout for that * keyboard. */ public int popupResId; /** Whether this key repeats(重复) itself when held down(按下) */ public boolean repeatable; //定义各种KEY_STATE所包含的按键的基本状态 private final static int[] KEY_STATE_NORMAL_ON = { android.R.attr.state_checkable, android.R.attr.state_checked }; private final static int[] KEY_STATE_PRESSED_ON = { android.R.attr.state_pressed, android.R.attr.state_checkable, android.R.attr.state_checked }; private final static int[] KEY_STATE_NORMAL_OFF = { android.R.attr.state_checkable }; private final static int[] KEY_STATE_PRESSED_OFF = { android.R.attr.state_pressed, android.R.attr.state_checkable }; private final static int[] KEY_STATE_NORMAL = { }; private final static int[] KEY_STATE_PRESSED = { android.R.attr.state_pressed }; /** Create an empty key with no attributes. */ public Key(Row parent) { keyboard = parent.parent; height = parent.defaultHeight; width = parent.defaultWidth; gap = parent.defaultHorizontalGap; edgeFlags = parent.rowEdgeFlags; } /** Create a key with the given top-left(左上角) coordinate and extract(获取;提取;提拔) * its attributes from the XML parser. * @param res resources associated with the caller's context(与调用者的Context相关的资源) * @param parent the row that this key belongs to. The row must already be attached to(联在一起) * a {@link Keyboard}.(必须是在键盘中定义了的行) * @param x the x coordinate of the top-left * @param y the y coordinate of the top-left * @param parser the XML parser containing the attributes for this key */ public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) { this(parent); this.x = x; this.y = y; TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), com.android.internal.R.styleable.Keyboard); width = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth); height = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyHeight, keyboard.mDisplayHeight, parent.defaultHeight); gap = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap); a.recycle(); a = res.obtainAttributes(Xml.asAttributeSet(parser), com.android.internal.R.styleable.Keyboard_Key); this.x += gap; TypedValue codesValue = new TypedValue();//键的code的类型 a.getValue(com.android.internal.R.styleable.Keyboard_Key_codes, codesValue);//Returns true if the value was retrieved, else false. if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) { codes = new int[] { codesValue.data }; } else if (codesValue.type == TypedValue.TYPE_STRING) { codes = parseCSV(codesValue.string.toString()); } iconPreview = a.getDrawable(com.android.internal.R.styleable.Keyboard_Key_iconPreview); if (iconPreview != null) { iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), iconPreview.getIntrinsicHeight());//Return the intrinsic height of the //underlying drawable object. Returns -1 if it has no intrinsic height, such as with a solid color. } popupCharacters = a.getText( com.android.internal.R.styleable.Keyboard_Key_popupCharacters); popupResId = a.getResourceId( com.android.internal.R.styleable.Keyboard_Key_popupKeyboard, 0); repeatable = a.getBoolean( com.android.internal.R.styleable.Keyboard_Key_isRepeatable, false); modifier = a.getBoolean( com.android.internal.R.styleable.Keyboard_Key_isModifier, false); sticky = a.getBoolean( com.android.internal.R.styleable.Keyboard_Key_isSticky, false); edgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Key_keyEdgeFlags, 0);//Return attribute int value, or defValue if not defined. edgeFlags |= parent.rowEdgeFlags;//a|=b等价于a=a|b icon = a.getDrawable( com.android.internal.R.styleable.Keyboard_Key_keyIcon); if (icon != null) { icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); } label = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyLabel);//CharSequence holding string data. May be styled. Returns null if the attribute is not defined. text = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyOutputText); if (codes == null && !TextUtils.isEmpty(label)) {//Returns true if the string(label) is null or 0-length. codes = new int[] { label.charAt(0) };//Returns the character at the specified index, with the first character having index zero. } a.recycle(); } /** * Informs the key that it has been pressed, in case it needs to change its appearance or * state. * @see #onReleased(boolean) */ public void onPressed() { pressed = !pressed; } /** * Changes the pressed state of the key. If it is a sticky key, it will also change the * toggled state of the key if the finger was release inside. * @param inside whether the finger was released inside the key * @see #onPressed() */ public void onReleased(boolean inside) { pressed = !pressed;//释放,非按下状态 if (sticky) { on = !on;//(粘滞键)弹起状态(!on) } } **//解析CSV,暂时没看懂。** int[] parseCSV(String value) { int count = 0; int lastIndex = 0; if (value.length() > 0) { count++; /** * 方法:int java.lang.String.indexOf(String subString, int start) * 源码:附在本文的最后面(附码1) * 说明:Searches in this string(value) for the index of the specified string. * The search for the string starts at the specified offset and moves * towards the end of this string. * 这里就是在value中,从第lastIndex + 1字符开始,向后查找“,”,查到一个count++。 * value.indexOf(",", lastIndex + 1)在查找结束的时候,返回-1。 * 最终的count = ","的个数 + 1. */ while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) { count++; } } int[] values = new int[count]; count = 0; //Constructs a new StringTokenizer(分词器) for the parameter string(字符串参数:value) using the specified delimiters(分隔符:","). StringTokenizer st = new StringTokenizer(value, ","); while (st.hasMoreTokens()) {//Returns true if unprocessed tokens remain. try { //st.nextToken(): Returns the next token in the string as a String. //Integer.parseInt(st.nextToken()): Parses the specified string as a signed decimal integer(有符十进制整数) value. The ASCII character \u002d ('-') is recognized as the minus sign. values[count++] = Integer.parseInt(st.nextToken()); } catch (NumberFormatException nfe) { Log.e(TAG, "Error parsing keycodes " + value); } } return values; } /** * Detects if a point falls inside this key. * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return whether or not the point falls inside the key. If the key is attached to an edge, * it will assume that all points between the key and the edge are considered to be inside * the key. */ public boolean isInside(int x, int y) { boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; boolean topEdge = (edgeFlags & EDGE_TOP) > 0; boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; if ((x >= this.x || (leftEdge && x <= this.x + this.width)) && (x < this.x + this.width || (rightEdge && x >= this.x)) && (y >= this.y || (topEdge && y <= this.y + this.height)) && (y < this.y + this.height || (bottomEdge && y >= this.y))) { return true; } else { return false; } } /** * Returns the square of the distance(距离的平方) between the center of the key * and the given point. * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return the square of the distance of the point from the center of the key */ public int squaredDistanceFrom(int x, int y) { int xDist = this.x + width / 2 - x; int yDist = this.y + height / 2 - y; return xDist * xDist + yDist * yDist; } /** * Returns the drawable state for the key, based on the current state and type of the key. * @return the drawable state of the key. * @see android.graphics.drawable.StateListDrawable#setState(int[]) */ public int[] getCurrentDrawableState() { int[] states = KEY_STATE_NORMAL; if (on) {//粘滞键处于粘滞状态 if (pressed) { states = KEY_STATE_PRESSED_ON; } else { states = KEY_STATE_NORMAL_ON; } } else { if (sticky) { if (pressed) { states = KEY_STATE_PRESSED_OFF; } else { states = KEY_STATE_NORMAL_OFF; } } else { if (pressed) { states = KEY_STATE_PRESSED; } } } return states; } } /** * Creates a keyboard from the given xml key layout file. * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. */ public Keyboard(Context context, int xmlLayoutResId) { this(context, xmlLayoutResId, 0); } /** * Creates a keyboard from the given xml key layout file. Weeds out rows * that have a keyboard mode defined but don't match the specified mode. * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. * @param modeId keyboard mode identifier * @param width sets width of keyboard * @param height sets height of keyboard */ public Keyboard(Context context, int xmlLayoutResId, int modeId, int width, int height) { mDisplayWidth = width; mDisplayHeight = height; mDefaultHorizontalGap = 0; mDefaultWidth = mDisplayWidth / 10; mDefaultVerticalGap = 0; mDefaultHeight = mDefaultWidth; mKeys = new ArrayList<Key>(); mModifierKeys = new ArrayList<Key>(); mKeyboardMode = modeId; loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); } /** * Creates a keyboard from the given xml key layout file. Weeds out rows * that have a keyboard mode defined but don't match the specified mode. * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. * @param modeId keyboard mode identifier */ public Keyboard(Context context, int xmlLayoutResId, int modeId) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); mDisplayWidth = dm.widthPixels; mDisplayHeight = dm.heightPixels; //Log.v(TAG, "keyboard's display metrics:" + dm); mDefaultHorizontalGap = 0; mDefaultWidth = mDisplayWidth / 10; mDefaultVerticalGap = 0; mDefaultHeight = mDefaultWidth; mKeys = new ArrayList<Key>(); mModifierKeys = new ArrayList<Key>(); mKeyboardMode = modeId; loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); } /** * <p>Creates a blank keyboard from the given resource file and * populates(定位,安置) it with the specified characters in left-to-right, top-to-bottom fashion, * using the specified number of columns. * </p> * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as * possible in each row.</p> * @param context the application or service context * @param layoutTemplateResId the layout template(模板) file, containing no keys. * @param characters the list of characters(需要显示的字符) to display on the keyboard. One key will be created * for each character. * @param columns the number of columns of keys to display. If this number is greater than the * number of keys that can fit in a row, it will be ignored. If this number is -1, the * keyboard will fit as many keys as possible in each row. */ public Keyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) { this(context, layoutTemplateResId); int x = 0; int y = 0; int column = 0; mTotalWidth = 0; Row row = new Row(this); row.defaultHeight = mDefaultHeight; row.defaultWidth = mDefaultWidth; row.defaultHorizontalGap = mDefaultHorizontalGap; row.verticalGap = mDefaultVerticalGap; row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; for (int i = 0; i < characters.length(); i++) { char c = characters.charAt(i); if (column >= maxColumns || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { x = 0; y += mDefaultVerticalGap + mDefaultHeight; column = 0; } final Key key = new Key(row); key.x = x; key.y = y; key.label = String.valueOf(c); key.codes = new int[] { c }; column++; x += key.width + key.gap; mKeys.add(key); row.mKeys.add(key); if (x > mTotalWidth) { mTotalWidth = x; } } mTotalHeight = y + mDefaultHeight; rows.add(row); } final void resize(int newWidth, int newHeight) { int numRows = rows.size(); for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { Row row = rows.get(rowIndex); int numKeys = row.mKeys.size(); int totalGap = 0; int totalWidth = 0; for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) { Key key = row.mKeys.get(keyIndex); if (keyIndex > 0) { totalGap += key.gap; } totalWidth += key.width; } if (totalGap + totalWidth > newWidth) { int x = 0; float scaleFactor = (float)(newWidth - totalGap) / totalWidth; for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) { Key key = row.mKeys.get(keyIndex); key.width *= scaleFactor; key.x = x; x += key.width + key.gap; } } } mTotalWidth = newWidth; // TODO: This does not adjust the vertical placement according to the new size. // The main problem in the previous code was horizontal placement/size, but we should // also recalculate the vertical sizes/positions when we get this resize call. } public List<Key> getKeys() { return mKeys; } public List<Key> getModifierKeys() { return mModifierKeys; } protected int getHorizontalGap() { return mDefaultHorizontalGap; } protected void setHorizontalGap(int gap) { mDefaultHorizontalGap = gap; } protected int getVerticalGap() { return mDefaultVerticalGap; } protected void setVerticalGap(int gap) { mDefaultVerticalGap = gap; } protected int getKeyHeight() { return mDefaultHeight; } protected void setKeyHeight(int height) { mDefaultHeight = height; } protected int getKeyWidth() { return mDefaultWidth; } protected void setKeyWidth(int width) { mDefaultWidth = width; } /** * Returns the total height of the keyboard * @return the total height of the keyboard */ public int getHeight() { return mTotalHeight; } public int getMinWidth() { return mTotalWidth; } public boolean setShifted(boolean shiftState) { for (Key shiftKey : mShiftKeys) { if (shiftKey != null) { shiftKey.on = shiftState; } } if (mShifted != shiftState) { mShifted = shiftState; return true; } return false; } public boolean isShifted() { return mShifted; } /** * @hide */ public int[] getShiftKeyIndices() { return mShiftKeyIndices; } public int getShiftKeyIndex() { return mShiftKeyIndices[0]; } private void computeNearestNeighbors() { // Round-up so we don't have any pixels outside the grid mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; mGridNeighbors = new int[GRID_SIZE][]; int[] indices = new int[mKeys.size()]; final int gridWidth = GRID_WIDTH * mCellWidth; final int gridHeight = GRID_HEIGHT * mCellHeight; for (int x = 0; x < gridWidth; x += mCellWidth) { for (int y = 0; y < gridHeight; y += mCellHeight) { int count = 0; for (int i = 0; i < mKeys.size(); i++) { final Key key = mKeys.get(i); if (key.squaredDistanceFrom(x, y) < mProximityThreshold || key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold || key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) < mProximityThreshold || key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) { indices[count++] = i; } } int [] cell = new int[count]; System.arraycopy(indices, 0, cell, 0, count); mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; } } } /** * Returns the indices 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 integer indices for the nearest keys to the given point. If the given * point is out of range, then an array of size zero is returned. */ public int[] getNearestKeys(int x, int y) { if (mGridNeighbors == null) computeNearestNeighbors(); if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); if (index < GRID_SIZE) { return mGridNeighbors[index]; } } return new int[0]; } protected Row createRowFromXml(Resources res, XmlResourceParser parser) { return new Row(res, this, parser); } protected Key createKeyFromXml(Resources res, Row parent, int x, int y, XmlResourceParser parser) { return new Key(res, parent, x, y, parser); } private void loadKeyboard(Context context, XmlResourceParser parser) { boolean inKey = false; boolean inRow = false; boolean leftMostKey = false; int row = 0; int x = 0; int y = 0; Key key = null; Row currentRow = null; Resources res = context.getResources(); boolean skipRow = false; try { int event; while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { if (event == XmlResourceParser.START_TAG) { String tag = parser.getName(); if (TAG_ROW.equals(tag)) { inRow = true; x = 0; currentRow = createRowFromXml(res, parser); rows.add(currentRow); skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; if (skipRow) { skipToEndOfRow(parser); inRow = false; } } else if (TAG_KEY.equals(tag)) { inKey = true; key = createKeyFromXml(res, currentRow, x, y, parser); mKeys.add(key); if (key.codes[0] == KEYCODE_SHIFT) { // Find available shift key slot(位置) and put this shift key in it for (int i = 0; i < mShiftKeys.length; i++) { if (mShiftKeys[i] == null) { mShiftKeys[i] = key; mShiftKeyIndices[i] = mKeys.size()-1; break; } } mModifierKeys.add(key); } else if (key.codes[0] == KEYCODE_ALT) { mModifierKeys.add(key); } currentRow.mKeys.add(key); } else if (TAG_KEYBOARD.equals(tag)) { parseKeyboardAttributes(res, parser); } } else if (event == XmlResourceParser.END_TAG) { if (inKey) { inKey = false; x += key.gap + key.width; if (x > mTotalWidth) { mTotalWidth = x; } } else if (inRow) { inRow = false; y += currentRow.verticalGap; y += currentRow.defaultHeight; row++; } else { // TODO: error or extend? } } } } catch (Exception e) { Log.e(TAG, "Parse error:" + e); e.printStackTrace(); } mTotalHeight = y - mDefaultVerticalGap; } private void skipToEndOfRow(XmlResourceParser parser) throws XmlPullParserException, IOException { int event; while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { if (event == XmlResourceParser.END_TAG && parser.getName().equals(TAG_ROW)) { break; } } } private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), com.android.internal.R.styleable.Keyboard); mDefaultWidth = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyWidth, mDisplayWidth, mDisplayWidth / 10); mDefaultHeight = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_keyHeight, mDisplayHeight, 50); mDefaultHorizontalGap = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_horizontalGap, mDisplayWidth, 0); mDefaultVerticalGap = getDimensionOrFraction(a, com.android.internal.R.styleable.Keyboard_verticalGap, mDisplayHeight, 0); mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE); mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison a.recycle(); } static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { TypedValue value = a.peekValue(index); if (value == null) return defValue; if (value.type == TypedValue.TYPE_DIMENSION) { return a.getDimensionPixelOffset(index, defValue); } else if (value.type == TypedValue.TYPE_FRACTION) { // Round it to avoid values like 47.9999 from getting truncated return Math.round(a.getFraction(index, base, base, defValue)); } return defValue; } }
附码1:package java.lang.String.indexOf(String subString, int start)
/** * Searches in this string for the index of the specified string. The search * for the string starts at the specified offset and moves towards the end * of this string. * * @param subString * the string to find. * @param start * the starting offset. * @return the index of the first character of the specified string in this * string, -1 if the specified string is not a substring. * @throws NullPointerException * if {@code subString} is {@code null}. */ public int indexOf(String subString, int start) { if (start < 0) { start = 0; } int subCount = subString.count; int _count = count; if (subCount > 0) { if (subCount + start > _count) { return -1; } char[] target = subString.value; int subOffset = subString.offset; char firstChar = target[subOffset]; int end = subOffset + subCount; while (true) { int i = indexOf(firstChar, start); if (i == -1 || subCount + i > _count) { return -1; // handles subCount > count || start >= count } int o1 = offset + i, o2 = subOffset; char[] _value = value; while (++o2 < end && _value[++o1] == target[o2]) { // Intentionally empty } if (o2 == end) { return i; } start = i + 1; } } return start < _count ? start : _count; }
附码2: package java.util;
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util; /** * Breaks a string into tokens; new code should probably use {@link String#split}. * * <blockquote> * <pre> * // Legacy code: * StringTokenizer st = new StringTokenizer("a:b:c", ":"); * while (st.hasMoreTokens()) { * System.err.println(st.nextToken()); * } * * // New code: * for (String token : "a:b:c".split(":")) { * System.err.println(token); * } * </pre> * </blockquote> * * @since 1.0 */ public class StringTokenizer implements Enumeration<Object> { private String string; private String delimiters; private boolean returnDelimiters; private int position; /** * Constructs a new {@code StringTokenizer} for the parameter string using * whitespace as the delimiter. The {@code returnDelimiters} flag is set to * {@code false}. * * @param string * the string to be tokenized. */ public StringTokenizer(String string) { this(string, " \t\n\r\f", false); } /** * Constructs a new {@code StringTokenizer} for the parameter string using * the specified delimiters. The {@code returnDelimiters} flag is set to * {@code false}. If {@code delimiters} is {@code null}, this constructor * doesn't throw an {@code Exception}, but later calls to some methods might * throw a {@code NullPointerException}. * * @param string * the string to be tokenized. * @param delimiters * the delimiters to use.(分隔符们:分隔符组成的字符串,例如:" \t\n\r\f") */ public StringTokenizer(String string, String delimiters) { this(string, delimiters, false);//false:分隔符本身不放入tokens中 } /** * Constructs a new {@code StringTokenizer} for the parameter string using * the specified delimiters, returning the delimiters as tokens if the * parameter {@code returnDelimiters} is {@code true}. If {@code delimiters} * is null this constructor doesn't throw an {@code Exception}, but later * calls to some methods might throw a {@code NullPointerException}. * * @param string * the string to be tokenized. * @param delimiters * the delimiters to use. * @param returnDelimiters * {@code true} to return each delimiter as a token. */ public StringTokenizer(String string, String delimiters, boolean returnDelimiters) { if (string != null) { this.string = string; this.delimiters = delimiters; this.returnDelimiters = returnDelimiters; this.position = 0; } else throw new NullPointerException(); } /** * Returns the number of unprocessed tokens remaining in the string. * * @return number of tokens that can be retreived before an {@code * Exception} will result from a call to {@code nextToken()}. */ public int countTokens() { int count = 0; boolean inToken = false; for (int i = position, length = string.length(); i < length; i++) { if (delimiters.indexOf(string.charAt(i), 0) >= 0) { if (returnDelimiters) count++; if (inToken) { count++; inToken = false; } } else { inToken = true; } } if (inToken) count++; return count; } /** * Returns {@code true} if unprocessed tokens remain. This method is * implemented in order to satisfy the {@code Enumeration} interface. * * @return {@code true} if unprocessed tokens remain. */ public boolean hasMoreElements() { return hasMoreTokens(); } /** * Returns {@code true} if unprocessed tokens remain. * * @return {@code true} if unprocessed tokens remain. */ public boolean hasMoreTokens() { if (delimiters == null) { throw new NullPointerException(); } int length = string.length(); if (position < length) { if (returnDelimiters) return true; // there is at least one character and even if // it is a delimiter it is a token // otherwise find a character which is not a delimiter for (int i = position; i < length; i++) if (delimiters.indexOf(string.charAt(i), 0) == -1) return true; } return false; } /** * Returns the next token in the string as an {@code Object}. This method is * implemented in order to satisfy the {@code Enumeration} interface. * * @return next token in the string as an {@code Object} * @throws NoSuchElementException * if no tokens remain. */ public Object nextElement() { return nextToken(); } /** * Returns the next token in the string as a {@code String}. * * @return next token in the string as a {@code String}. * @throws NoSuchElementException * if no tokens remain. */ public String nextToken() { if (delimiters == null) { throw new NullPointerException(); } int i = position; int length = string.length(); if (i < length) { if (returnDelimiters) { if (delimiters.indexOf(string.charAt(position), 0) >= 0) return String.valueOf(string.charAt(position++)); for (position++; position < length; position++) if (delimiters.indexOf(string.charAt(position), 0) >= 0) return string.substring(i, position); return string.substring(i); } while (i < length && delimiters.indexOf(string.charAt(i), 0) >= 0) i++; position = i; if (i < length) { for (position++; position < length; position++) if (delimiters.indexOf(string.charAt(position), 0) >= 0) return string.substring(i, position); return string.substring(i); } } throw new NoSuchElementException(); } /** * Returns the next token in the string as a {@code String}. The delimiters * used are changed to the specified delimiters. * * @param delims * the new delimiters to use. * @return next token in the string as a {@code String}. * @throws NoSuchElementException * if no tokens remain. */ public String nextToken(String delims) { this.delimiters = delims; return nextToken(); } }
时间: 2024-10-10 03:06:40