Keyboard XML file supports include and merge tag
Keyboard XML file can include other keyboard XML file using directive <include keyboardLayout="@xml/...">. The keyboard XML file which is included must have <merge> tag as root element. Change-Id: I06c35fe7b3db5232acdb33f73a79f38d31261b32main
parent
4d9add5482
commit
d663555c9f
|
@ -129,4 +129,8 @@
|
||||||
requested keyboard mode, the row will be skipped. -->
|
requested keyboard mode, the row will be skipped. -->
|
||||||
<attr name="keyboardMode" format="reference" />
|
<attr name="keyboardMode" format="reference" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="BaseKeyboard_Include">
|
||||||
|
<attr name="keyboardLayout" format="reference" />
|
||||||
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -24,17 +24,17 @@ import android.content.res.TypedArray;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
import android.util.DisplayMetrics;
|
import android.view.InflateException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
||||||
* consists of rows of keys.
|
* consists of rows of keys.
|
||||||
|
@ -61,6 +61,8 @@ public class BaseKeyboard {
|
||||||
private static final String TAG_KEYBOARD = "Keyboard";
|
private static final String TAG_KEYBOARD = "Keyboard";
|
||||||
private static final String TAG_ROW = "Row";
|
private static final String TAG_ROW = "Row";
|
||||||
private static final String TAG_KEY = "Key";
|
private static final String TAG_KEY = "Key";
|
||||||
|
private static final String TAG_INCLUDE = "include";
|
||||||
|
private static final String TAG_MERGE = "merge";
|
||||||
|
|
||||||
public static final int EDGE_LEFT = 0x01;
|
public static final int EDGE_LEFT = 0x01;
|
||||||
public static final int EDGE_RIGHT = 0x02;
|
public static final int EDGE_RIGHT = 0x02;
|
||||||
|
@ -104,9 +106,6 @@ public class BaseKeyboard {
|
||||||
/** List of keys in this keyboard */
|
/** List of keys in this keyboard */
|
||||||
private final List<Key> mKeys = new ArrayList<Key>();
|
private final List<Key> mKeys = new ArrayList<Key>();
|
||||||
|
|
||||||
/** List of modifier keys such as Shift & Alt, if any */
|
|
||||||
private final List<Key> mModifierKeys = new ArrayList<Key>();
|
|
||||||
|
|
||||||
/** Width of the screen available to fit the keyboard */
|
/** Width of the screen available to fit the keyboard */
|
||||||
private final int mDisplayWidth;
|
private final int mDisplayWidth;
|
||||||
|
|
||||||
|
@ -177,8 +176,7 @@ public class BaseKeyboard {
|
||||||
a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
R.styleable.BaseKeyboard_Row);
|
R.styleable.BaseKeyboard_Row);
|
||||||
rowEdgeFlags = a.getInt(R.styleable.BaseKeyboard_Row_rowEdgeFlags, 0);
|
rowEdgeFlags = a.getInt(R.styleable.BaseKeyboard_Row_rowEdgeFlags, 0);
|
||||||
mode = a.getResourceId(R.styleable.BaseKeyboard_Row_keyboardMode,
|
mode = a.getResourceId(R.styleable.BaseKeyboard_Row_keyboardMode, 0);
|
||||||
0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,10 +556,6 @@ public class BaseKeyboard {
|
||||||
return mKeys;
|
return mKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Key> getModifierKeys() {
|
|
||||||
return mModifierKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getHorizontalGap() {
|
protected int getHorizontalGap() {
|
||||||
return mDefaultHorizontalGap;
|
return mDefaultHorizontalGap;
|
||||||
}
|
}
|
||||||
|
@ -682,80 +676,171 @@ public class BaseKeyboard {
|
||||||
return new Key(res, parent, x, y, parser);
|
return new Key(res, parent, x, y, parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadKeyboard(Context context, XmlResourceParser parser) {
|
private static class KeyboardParseState {
|
||||||
boolean inKey = false;
|
private final int mKeyboardMode;
|
||||||
boolean inRow = false;
|
private int mCurrentX = 0;
|
||||||
int row = 0;
|
private int mCurrentY = 0;
|
||||||
int x = 0;
|
private int mMaxRowWidth = 0;
|
||||||
int y = 0;
|
private int mTotalHeight = 0;
|
||||||
Key key = null;
|
private Row mCurrentRow = null;
|
||||||
Row currentRow = null;
|
|
||||||
Resources res = context.getResources();
|
|
||||||
boolean skipRow = false;
|
|
||||||
|
|
||||||
|
public KeyboardParseState(int keyboardMode) {
|
||||||
|
mKeyboardMode = keyboardMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return mCurrentX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return mCurrentY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Row getRow() {
|
||||||
|
return mCurrentRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if the row is valid for this keyboard mode
|
||||||
|
public boolean startRow(Row row) {
|
||||||
|
mCurrentX = 0;
|
||||||
|
mCurrentRow = row;
|
||||||
|
return row.mode == 0 || row.mode == mKeyboardMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skipRow() {
|
||||||
|
mCurrentRow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endRow() {
|
||||||
|
if (mCurrentRow == null)
|
||||||
|
throw new InflateException("orphant end row tag");
|
||||||
|
mCurrentY += mCurrentRow.verticalGap + mCurrentRow.defaultHeight;
|
||||||
|
mCurrentRow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endKey(Key key) {
|
||||||
|
mCurrentX += key.gap + key.width;
|
||||||
|
if (mCurrentX > mMaxRowWidth)
|
||||||
|
mMaxRowWidth = mCurrentX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endKeyboard(int defaultVerticalGap) {
|
||||||
|
mTotalHeight = mCurrentY - defaultVerticalGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxRowWidth() {
|
||||||
|
return mMaxRowWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalHeight() {
|
||||||
|
return mTotalHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadKeyboard(Context context, XmlResourceParser parser) {
|
||||||
try {
|
try {
|
||||||
|
KeyboardParseState state = new KeyboardParseState(mKeyboardMode);
|
||||||
|
parseKeyboard(context.getResources(), parser, state);
|
||||||
|
// mTotalWidth is the width of this keyboard which is maximum width of row.
|
||||||
|
mTotalWidth = state.getMaxRowWidth();
|
||||||
|
mTotalHeight = state.getTotalHeight();
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseKeyboard(Resources res, XmlResourceParser parser, KeyboardParseState state)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
Key key = null;
|
||||||
|
|
||||||
int event;
|
int event;
|
||||||
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||||
if (event == XmlResourceParser.START_TAG) {
|
if (event == XmlResourceParser.START_TAG) {
|
||||||
String tag = parser.getName();
|
String tag = parser.getName();
|
||||||
if (TAG_ROW.equals(tag)) {
|
if (TAG_ROW.equals(tag)) {
|
||||||
inRow = true;
|
|
||||||
x = 0;
|
|
||||||
// TODO createRowFromXml should not be called from BaseKeyboard constructor.
|
// TODO createRowFromXml should not be called from BaseKeyboard constructor.
|
||||||
currentRow = createRowFromXml(res, parser);
|
Row row = createRowFromXml(res, parser);
|
||||||
skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
|
if (!state.startRow(row))
|
||||||
if (skipRow) {
|
skipToEndOfRow(parser, state);
|
||||||
skipToEndOfRow(parser);
|
|
||||||
inRow = false;
|
|
||||||
}
|
|
||||||
} else if (TAG_KEY.equals(tag)) {
|
} else if (TAG_KEY.equals(tag)) {
|
||||||
inKey = true;
|
|
||||||
// TODO createKeyFromXml should not be called from BaseKeyboard constructor.
|
// TODO createKeyFromXml should not be called from BaseKeyboard constructor.
|
||||||
key = createKeyFromXml(res, currentRow, x, y, parser);
|
key = createKeyFromXml(res, state.getRow(), state.getX(), state.getY(),
|
||||||
|
parser);
|
||||||
mKeys.add(key);
|
mKeys.add(key);
|
||||||
if (key.codes[0] == KEYCODE_SHIFT) {
|
if (key.codes[0] == KEYCODE_SHIFT)
|
||||||
mShiftKeys.add(key);
|
mShiftKeys.add(key);
|
||||||
mModifierKeys.add(key);
|
|
||||||
} else if (key.codes[0] == KEYCODE_ALT) {
|
|
||||||
mModifierKeys.add(key);
|
|
||||||
}
|
|
||||||
} else if (TAG_KEYBOARD.equals(tag)) {
|
} else if (TAG_KEYBOARD.equals(tag)) {
|
||||||
parseKeyboardAttributes(res, parser);
|
parseKeyboardAttributes(res, parser);
|
||||||
|
} else if (TAG_INCLUDE.equals(tag)) {
|
||||||
|
if (parser.getDepth() == 0)
|
||||||
|
throw new InflateException("<include /> cannot be the root element");
|
||||||
|
parseInclude(res, parser, state);
|
||||||
|
} else if (TAG_MERGE.equals(tag)) {
|
||||||
|
throw new InflateException("<merge> must not be appeared in keyboard XML file");
|
||||||
|
} else {
|
||||||
|
throw new InflateException("unknown start tag: " + tag);
|
||||||
}
|
}
|
||||||
} else if (event == XmlResourceParser.END_TAG) {
|
} else if (event == XmlResourceParser.END_TAG) {
|
||||||
if (inKey) {
|
String tag = parser.getName();
|
||||||
inKey = false;
|
if (TAG_KEY.equals(tag)) {
|
||||||
x += key.gap + key.width;
|
state.endKey(key);
|
||||||
if (x > mTotalWidth) {
|
} else if (TAG_ROW.equals(tag)) {
|
||||||
mTotalWidth = x;
|
state.endRow();
|
||||||
}
|
} else if (TAG_KEYBOARD.equals(tag)) {
|
||||||
} else if (inRow) {
|
state.endKeyboard(mDefaultVerticalGap);
|
||||||
inRow = false;
|
} else if (TAG_INCLUDE.equals(tag)) {
|
||||||
y += currentRow.verticalGap;
|
;
|
||||||
y += currentRow.defaultHeight;
|
} else if (TAG_MERGE.equals(tag)) {
|
||||||
row++;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// TODO: error or extend?
|
throw new InflateException("unknown end tag: " + tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Parse error:" + e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
mTotalHeight = y - mDefaultVerticalGap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void skipToEndOfRow(XmlResourceParser parser)
|
private void parseInclude(Resources res, XmlResourceParser parent, KeyboardParseState state)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
final TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parent),
|
||||||
|
R.styleable.BaseKeyboard_Include);
|
||||||
|
final int keyboardLayout = a.getResourceId(
|
||||||
|
R.styleable.BaseKeyboard_Include_keyboardLayout, 0);
|
||||||
|
a.recycle();
|
||||||
|
if (keyboardLayout == 0)
|
||||||
|
throw new InflateException("<include /> must have keyboardLayout attribute");
|
||||||
|
final XmlResourceParser parser = res.getLayout(keyboardLayout);
|
||||||
|
|
||||||
|
int event;
|
||||||
|
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||||
|
if (event == XmlResourceParser.START_TAG) {
|
||||||
|
String name = parser.getName();
|
||||||
|
if (TAG_MERGE.equals(name)) {
|
||||||
|
parseKeyboard(res, parser, state);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new InflateException(
|
||||||
|
"include keyboard layout must have <merge> root element");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipToEndOfRow(XmlResourceParser parser, KeyboardParseState state)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
int event;
|
int event;
|
||||||
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
|
||||||
if (event == XmlResourceParser.END_TAG
|
if (event == XmlResourceParser.END_TAG) {
|
||||||
&& parser.getName().equals(TAG_ROW)) {
|
String tag = parser.getName();
|
||||||
break;
|
if (TAG_ROW.equals(tag)) {
|
||||||
|
state.skipRow();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new InflateException("can not find </Row>");
|
||||||
|
}
|
||||||
|
|
||||||
private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
|
private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
|
||||||
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
|
|
Loading…
Reference in New Issue