MTKKeyboardView.java
java
public class MTKKeyboardView extends View implements View.OnClickListener {
public interface OnKeyboardActionListener {
void onPress(int primaryCode);
void onRelease(int primaryCode);
void onKey(int primaryCode, int[] keyCodes);
void onText(CharSequence text);
}
private static final String TAG = "MTKKeyboardView";
private static final boolean DEBUG = false;
private static final int NOT_A_KEY = -1;
private static final int[] KEY_DELETE = {MTKKeyboard.KEYCODE_DELETE};
private static final int[] LONG_PRESSABLE_STATE_SET = {R.attr.state_long_pressable};
private MTKKeyboard mKeyboard;
private int mCurrentKeyIndex = 0;
private int mLabelTextSize;
private int mKeyTextSize;
private int mKeyTextColor;
private float mShadowRadius;
private int mShadowColor;
private float mBackgroundDimAmount;
private int[] mOffsetInWindow;
private PopupWindow mPopupKeyboard;
private View mMiniKeyboardContainer;
private MTKKeyboardView mMiniKeyboard;
private boolean mMiniKeyboardOnScreen;
private View mPopupParent;
private int mMiniKeyboardOffsetX;
private int mMiniKeyboardOffsetY;
private Map<Key, View> mMiniKeyboardCache;
private int[] mWindowOffset;
private Key[] mKeys;
private OnKeyboardActionListener mKeyboardActionListener;
private static final int MSG_SHOW_PREVIEW = 1;
private static final int MSG_REMOVE_PREVIEW = 2;
private static final int MSG_REPEAT = 3;
private static final int MSG_LONGPRESS = 4;
private static final int DELAY_BEFORE_PREVIEW = 0;
private static final int DELAY_AFTER_PREVIEW = 70;
private int mVerticalCorrection;
private int mProximityThreshold;
private boolean mPreviewCentered = false;
private boolean mShowPreview = true;
private boolean mShowTouchPoints = true;
private int mPopupPreviewX;
private int mPopupPreviewY;
private int mLastX;
private int mLastY;
private int mStartX;
private int mStartY;
private boolean mProximityCorrectOn;
private Paint mPaint;
private Rect mPadding;
private long mDownTime;
private long mLastMoveTime;
private int mLastKey;
private int mLastCodeX;
private int mLastCodeY;
private int mCurrentKey = 0;
private int mDownKey = NOT_A_KEY;
private long mLastKeyTime;
private long mCurrentKeyTime;
private int[] mKeyIndices = new int[12];
private GestureDetector mGestureDetector;
private int mPopupX;
private int mPopupY;
private int mRepeatKeyIndex = NOT_A_KEY;
private int mPopupLayout;
private boolean mAbortKey;
private Key mInvalidatedKey;
private Rect mClipRegion = new Rect(0, 0, 0, 0);
private boolean mPossiblePoly;
private SwipeTracker mSwipeTracker = new SwipeTracker();
private int mSwipeThreshold;
private boolean mDisambiguateSwipe;
private int mOldPointerCount = 1;
private float mOldPointerX;
private float mOldPointerY;
private Drawable mKeyBackground;
private static final int REPEAT_INTERVAL = 50;
private static final int REPEAT_START_DELAY = 400;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration
.getLongPressTimeout();
private static int MAX_NEARBY_KEYS = 12;
private int[] mDistances = new int[MAX_NEARBY_KEYS];
private int mLastSentIndex;
private int mTapCount;
private long mLastTapTime;
private boolean mInMultiTap;
private static final int MULTITAP_INTERVAL = 800;
private StringBuilder mPreviewLabel = new StringBuilder(1);
private boolean mDrawPending;
private Rect mDirtyRect = new Rect();
private Bitmap mBuffer;
private boolean mKeyboardChanged;
private Canvas mCanvas;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_PREVIEW:
showKey(msg.arg1);
break;
case MSG_REPEAT:
if (repeatKey()) {
Message repeat = Message.obtain(this, MSG_REPEAT);
sendMessageDelayed(repeat, REPEAT_INTERVAL);
}
break;
case MSG_LONGPRESS:
openPopupIfRequired((MotionEvent) msg.obj);
break;
}
}
};
private int mCurrentRow = 0;
private int mCurrentColumn = 0;
public MTKKeyboardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
public MTKKeyboardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.KeyboardView, defStyle, 0);
/* LayoutInflater inflate = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int previewLayout = 0;*/
int keyTextSize = 0;
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.KeyboardView_keyBackground:
mKeyBackground = a.getDrawable(attr);
break;
case R.styleable.KeyboardView_verticalCorrection:
mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
break;
case R.styleable.KeyboardView_keyPreviewLayout:
//previewLayout = a.getResourceId(attr, 0);
break;
case R.styleable.KeyboardView_keyTextSize:
mKeyTextSize = a.getDimensionPixelSize(attr, 18);
break;
case R.styleable.KeyboardView_keyTextColor:
mKeyTextColor = a.getColor(attr, 0xFF000000);
break;
case R.styleable.KeyboardView_labelTextSize:
mLabelTextSize = a.getDimensionPixelSize(attr, 14);
break;
case R.styleable.KeyboardView_popupLayout:
mPopupLayout = a.getResourceId(attr, 0);
break;
case R.styleable.KeyboardView_shadowColor:
mShadowColor = a.getColor(attr, 0);
break;
case R.styleable.KeyboardView_shadowRadius:
mShadowRadius = a.getFloat(attr, 0f);
break;
}
}
// a = context.obtainStyledAttributes(
// com.android.internal.R.styleable.Theme);
// mBackgroundDimAmount =
// a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
mBackgroundDimAmount = 0.5f;
mPopupKeyboard = new PopupWindow(context);
mPopupKeyboard.setBackgroundDrawable(null);
// mPopupKeyboard.setClippingEnabled(false);
mPopupParent = this;
// mPredicting = true;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(keyTextSize);
mPaint.setTextAlign(Align.CENTER);
mPaint.setAlpha(255);
mPadding = new Rect(0, 0, 0, 0);
mMiniKeyboardCache = new HashMap<Key, View>();
if (mKeyBackground != null) {
mKeyBackground.getPadding(mPadding);
}
mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
// mDisambiguateSwipe = getResources().getBoolean(
// com.android.internal.R.bool.config_swipeDisambiguation);
mDisambiguateSwipe = false;
resetMultiTap();
initGestureDetector();
}
private void initGestureDetector() {
mGestureDetector = new GestureDetector(getContext(),
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent me1, MotionEvent me2,
float velocityX, float velocityY) {
if (mPossiblePoly)
return false;
final float absX = Math.abs(velocityX);
final float absY = Math.abs(velocityY);
float deltaX = me2.getX() - me1.getX();
float deltaY = me2.getY() - me1.getY();
int travelX = getWidth() / 2;
int travelY = getHeight() / 2;
// height
mSwipeTracker.computeCurrentVelocity(1000);
final float endingVelocityX = mSwipeTracker
.getXVelocity();
final float endingVelocityY = mSwipeTracker
.getYVelocity();
boolean sendDownKey = false;
if (velocityX > mSwipeThreshold && absY < absX
&& deltaX > travelX) {
if (mDisambiguateSwipe
&& endingVelocityX < velocityX / 4) {
sendDownKey = true;
} else {
return true;
}
} else if (velocityX < -mSwipeThreshold && absY < absX
&& deltaX < -travelX) {
if (mDisambiguateSwipe
&& endingVelocityX > velocityX / 4) {
sendDownKey = true;
} else {
return true;
}
} else if (velocityY < -mSwipeThreshold && absX < absY
&& deltaY < -travelY) {
if (mDisambiguateSwipe
&& endingVelocityY > velocityY / 4) {
sendDownKey = true;
} else {
return true;
}
} else if (velocityY > mSwipeThreshold
&& absX < absY / 2 && deltaY > travelY) {
if (mDisambiguateSwipe
&& endingVelocityY < velocityY / 4) {
sendDownKey = true;
} else {
return true;
}
}
if (sendDownKey) {
detectAndSendKey(mDownKey, mStartX, mStartY, me1
.getEventTime());
}
return false;
}
});
mGestureDetector.setIsLongpressEnabled(false);
}
public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
mKeyboardActionListener = listener;
}
protected OnKeyboardActionListener getOnKeyboardActionListener() {
return mKeyboardActionListener;
}
public void setKeyboard(MTKKeyboard keyboard) {
if (mKeyboard != null) {
showPreview(NOT_A_KEY);
}
removeMessages();
mKeyboard = keyboard;
List<Key> keys = mKeyboard.getKeys();
mKeys = keys.toArray(new Key[keys.size()]);
requestLayout();
mKeyboardChanged = true;
invalidateAllKeys();
computeProximityThreshold(keyboard);
mMiniKeyboardCache.clear();
mAbortKey = true;
}
public MTKKeyboard getKeyboard() {
return mKeyboard;
}
public boolean setShifted(boolean shifted) {
if (mKeyboard != null) {
if (mKeyboard.setShifted(shifted)) {
invalidateAllKeys();
return true;
}
}
return false;
}
public boolean isShifted() {
if (mKeyboard != null) {
return mKeyboard.isShifted();
}
return false;
}
public void setPreviewEnabled(boolean previewEnabled) {
mShowPreview = previewEnabled;
}
public boolean isPreviewEnabled() {
return mShowPreview;
}
public void setVerticalCorrection(int verticalOffset) {
}
public void setPopupParent(View v) {
mPopupParent = v;
}
public void setPopupOffset(int x, int y) {
mMiniKeyboardOffsetX = x;
mMiniKeyboardOffsetY = y;
}
public void setProximityCorrectionEnabled(boolean enabled) {
mProximityCorrectOn = enabled;
}
public boolean isProximityCorrectionEnabled() {
return mProximityCorrectOn;
}
public void onClick(View v) {
dismissPopupKeyboard();
}
private CharSequence adjustCase(CharSequence label) {
if (mKeyboard.isShifted() && label != null && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
}
return label;
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mKeyboard == null) {
setMeasuredDimension(getPaddingLeft() + getPaddingRight(),
getPaddingTop() + getPaddingBottom());
} else {
int width = mKeyboard.getMinWidth() + getPaddingLeft()
+ getPaddingRight();
if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop()
+ getPaddingBottom());
}
}
private void computeProximityThreshold(MTKKeyboard keyboard) {
if (keyboard == null)
return;
final Key[] keys = mKeys;
if (keys == null)
return;
int length = keys.length;
int dimensionSum = 0;
for (int i = 0; i < length; i++) {
Key key = keys[i];
dimensionSum += Math.min(key.width, key.height) + key.gap;
}
if (dimensionSum < 0 || length == 0)
return;
mProximityThreshold = (int) (dimensionSum * 1.4f / length);
mProximityThreshold *= mProximityThreshold;
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBuffer = null;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawPending || mBuffer == null || mKeyboardChanged) {
onBufferDraw();
}
canvas.drawBitmap(mBuffer, 0, 0, null);
}
private void onBufferDraw() {
if (mBuffer == null || mKeyboardChanged) {
if (mBuffer == null
|| mKeyboardChanged
&& (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
mBuffer = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
}
invalidateAllKeys();
mKeyboardChanged = false;
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect);
if (mKeyboard == null)
return;
final Paint paint = mPaint;
// final Drawable keyBackground = mKeyBackground;
final Rect clipRegion = mClipRegion;
final Rect padding = mPadding;
final int kbdPaddingLeft = getPaddingLeft();
final int kbdPaddingTop = getPaddingTop();
final Key[] keys = mKeys;
final Key invalidKey = mInvalidatedKey;
paint.setColor(mKeyTextColor);
boolean drawSingleKey = false;
if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left
&& invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top
&& invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right
&& invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
drawSingleKey = true;
}
}
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[i];
if (drawSingleKey && !invalidKey.equals(key)) {
continue;
}
int[] drawableState = key.getCurrentDrawableState();
if (drawableState.length > 0
&& android.R.attr.state_pressed == drawableState[0]) {
paint.setColor(Color.RED);
} else {
paint.setColor(Color.WHITE);
}
// keyBackground.setState(drawableState);
String label = key.label == null ? null : adjustCase(key.label)
.toString();
// final Rect bounds = keyBackground.getBounds();
// if (key.width != bounds.right || key.height != bounds.bottom) {
// keyBackground.setBounds(0, 0, key.width, key.height);
// }
canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
// keyBackground.draw(canvas);
if (label != null) {
if (label.length() > 1 && key.codes.length < 2 && mLabelTextSize > 0) {
paint.setTextSize(mLabelTextSize);
paint.setTypeface(Typeface.DEFAULT_BOLD);
} else {
paint.setTextSize(mKeyTextSize);
paint.setTypeface(Typeface.DEFAULT);
}
paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
canvas.drawText(label,
(key.width - padding.left - padding.right) / 2
+ padding.left,
(key.height - padding.top - padding.bottom) / 2
+ (paint.getTextSize() - paint.descent()) / 2
+ padding.top, paint);
paint.setShadowLayer(0, 0, 0, 0);
} else if (key.icon != null) {
final int drawableX = (key.width - padding.left - padding.right - key.icon
.getIntrinsicWidth())
/ 2 + padding.left;
final int drawableY = (key.height - padding.top
- padding.bottom - key.icon.getIntrinsicHeight())
/ 2 + padding.top;
canvas.translate(drawableX, drawableY);
key.icon.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon
.getIntrinsicHeight());
key.icon.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
}
mInvalidatedKey = null;
if (mMiniKeyboardOnScreen) {
paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
mDrawPending = false;
mDirtyRect.setEmpty();
}
private int getKeyIndices(int x, int y, int[] allKeys) {
final Key[] keys = mKeys;
int primaryIndex = NOT_A_KEY;
int closestKey = NOT_A_KEY;
int closestKeyDist = mProximityThreshold + 1;
java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
int[] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
final int keyCount = nearestKeyIndices.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(x, y);
if (((mProximityCorrectOn && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) || isInside)
&& key.codes[0] > 32) {
final int nCodes = key.codes.length;
if (dist < closestKeyDist) {
closestKeyDist = dist;
closestKey = nearestKeyIndices[i];
}
if (allKeys == null)
continue;
for (int j = 0; j < mDistances.length; j++) {
if (mDistances[j] > dist) {
System.arraycopy(mDistances, j, mDistances, j + nCodes,
mDistances.length - j - nCodes);
System.arraycopy(allKeys, j, allKeys, j + nCodes,
allKeys.length - j - nCodes);
for (int c = 0; c < nCodes; c++) {
allKeys[j + c] = key.codes[c];
mDistances[j + c] = dist;
}
break;
}
}
}
if (isInside) {
primaryIndex = nearestKeyIndices[i];
}
}
if (primaryIndex == NOT_A_KEY) {
primaryIndex = closestKey;
}
return primaryIndex;
}
private void detectAndSendKey(int index, int x, int y, long eventTime) {
if (index != NOT_A_KEY && index < mKeys.length) {
final Key key = mKeys[index];
if (key.text != null) {
mKeyboardActionListener.onText(key.text);
mKeyboardActionListener.onRelease(NOT_A_KEY);
} else {
int code = key.codes[0];
// TextEntryState.keyPressedAt(key, x, y);
int[] codes = new int[MAX_NEARBY_KEYS];
Arrays.fill(codes, NOT_A_KEY);
getKeyIndices(x, y, codes);
if (mInMultiTap) {
if (mTapCount != -1) {
mKeyboardActionListener.onKey(
MTKKeyboard.KEYCODE_DELETE, KEY_DELETE);
} else {
mTapCount = 0;
}
code = key.codes[mTapCount];
}
mKeyboardActionListener.onKey(code, codes);
mKeyboardActionListener.onRelease(code);
}
mLastSentIndex = index;
mLastTapTime = eventTime;
}
}
CharSequence getPreviewText(Key key) {
if (mInMultiTap) {
mPreviewLabel.setLength(0);
mPreviewLabel
.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
return adjustCase(mPreviewLabel);
} else {
return adjustCase(key.label);
}
}
private void showPreview(int keyIndex) {
int oldKeyIndex = mCurrentKeyIndex;
mCurrentKeyIndex = keyIndex;
final Key[] keys = mKeys;
if (oldKeyIndex != mCurrentKeyIndex) {
if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
invalidateKey(oldKeyIndex);
}
if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
keys[mCurrentKeyIndex].onPressed();
invalidateKey(mCurrentKeyIndex);
}
}
if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
mHandler.removeMessages(MSG_SHOW_PREVIEW);
}
}
private void showKey(final int keyIndex) {
//final Key[] keys = mKeys;
if (keyIndex < 0 || keyIndex >= mKeys.length)
return;
// Key key = keys[keyIndex];
mHandler.removeMessages(MSG_REMOVE_PREVIEW);
if (mOffsetInWindow == null) {
mOffsetInWindow = new int[2];
getLocationInWindow(mOffsetInWindow);
mOffsetInWindow[0] += mMiniKeyboardOffsetX;
mOffsetInWindow[1] += mMiniKeyboardOffsetY;
}
}
public void invalidateAllKeys() {
mDirtyRect.union(0, 0, getWidth(), getHeight());
mDrawPending = true;
invalidate();
}
public void invalidateKey(int keyIndex) {
if (mKeys == null)
return;
if (keyIndex < 0 || keyIndex >= mKeys.length) {
return;
}
final Key key = mKeys[keyIndex];
mInvalidatedKey = key;
mDirtyRect.union(0, 0, getWidth(), getHeight());
//mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
// key.x + key.width + getPaddingLeft(), key.y + key.height
// + getPaddingTop());
onBufferDraw();
//invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), key.x
// + key.width + getPaddingLeft(), key.y + key.height
// + getPaddingTop());
invalidate();
}
private boolean openPopupIfRequired(MotionEvent me) {
if (mPopupLayout == 0) {
return false;
}
if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
return false;
}
Key popupKey = mKeys[mCurrentKey];
boolean result = onLongPress(popupKey);
if (result) {
mAbortKey = true;
showPreview(NOT_A_KEY);
}
return result;
}
protected boolean onLongPress(Key popupKey) {
if (mWindowOffset == null) {
mWindowOffset = new int[2];
getLocationInWindow(mWindowOffset);
}
mPopupX = popupKey.x + getPaddingLeft();
mPopupY = popupKey.y + getPaddingTop();
mPopupX = mPopupX + popupKey.width
- mMiniKeyboardContainer.getMeasuredWidth();
mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight()
+ mWindowOffset[0];
final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom()
+ mWindowOffset[1];
mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
mMiniKeyboard.setShifted(isShifted());
mPopupKeyboard.setContentView(mMiniKeyboardContainer);
mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
mMiniKeyboardOnScreen = true;
// mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
invalidateAllKeys();
return true;
}
private long mOldEventTime;
private boolean mUsedVelocity;
@Override
protected void onFocusChanged(boolean gainFocus, int direction,
Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (gainFocus) {
showPreview(mCurrentKey);
} else {
showPreview(NOT_A_KEY);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
MtkLog.d(TAG, "CurrentColumn Before = " + mCurrentColumn);
MtkLog.d(TAG, "CurrentRow Before = " + mCurrentRow);
MtkLog.d(TAG, "CurrentRowColunns Before = "
+ mKeyboard.getColumnCount()[mCurrentRow]);
MtkLog.d(TAG, "CurrentKey Before = " + mCurrentKey);
boolean consumed = true;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mCurrentColumn == 0) {
mCurrentColumn = mKeyboard.getColumnCount()[mCurrentRow] - 1;
mCurrentKey = mCurrentKey + mCurrentColumn;
} else {
mCurrentKey--;
mCurrentColumn--;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (mCurrentColumn == mKeyboard.getColumnCount()[mCurrentRow] - 1) {
mCurrentColumn = 0;
mCurrentKey = mCurrentKey
- mKeyboard.getColumnCount()[mCurrentRow] + 1;
} else {
mCurrentKey++;
mCurrentColumn++;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (mCurrentRow == 0) {
// ((View) getParent()).requestFocus();
consumed = false;
} else {
mCurrentRow--;
if (mCurrentColumn >= mKeyboard.getColumnCount()[mCurrentRow]) {
mCurrentKey -= mCurrentColumn + 1;
mCurrentColumn = mKeyboard.getColumnCount()[mCurrentRow] - 1;
} else {
mCurrentKey -= mKeyboard.getColumnCount()[mCurrentRow];
}
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mCurrentRow == mKeyboard.getRowCount() - 1) {
// ((View) getParent()).requestFocus();
consumed = false;
} else {
mCurrentRow++;
if (mCurrentColumn >= mKeyboard.getColumnCount()[mCurrentRow]) {
mCurrentKey += mKeyboard.getColumnCount()[mCurrentRow - 1]
- (mCurrentColumn
- mKeyboard.getColumnCount()[mCurrentRow] + 1);
mCurrentColumn = mKeyboard.getColumnCount()[mCurrentRow] - 1;
} else {
mCurrentKey += mKeyboard.getColumnCount()[mCurrentRow - 1];
}
}
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
final Key key = mKeys[mCurrentKey];
mKeyboardActionListener.onKey(key.codes[0], key.codes);
break;
default:
consumed = false;
break;
}
if (consumed) {
showPreview(mCurrentKey);
MtkLog.d(TAG, "CurrentColumn After = " + mCurrentColumn);
MtkLog.d(TAG, "CurrentRow After = " + mCurrentRow);
MtkLog.d(TAG, "CurrentRowColunns After = "
+ mKeyboard.getColumnCount()[mCurrentRow]);
MtkLog.d(TAG, "CurrentKey After = " + mCurrentKey);
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onTouchEvent(MotionEvent me) {
final int pointerCount = me.getPointerCount();
final int action = me.getAction();
boolean result;
final long now = me.getEventTime();
if (pointerCount != mOldPointerCount) {
if (pointerCount == 1) {
MotionEvent down = MotionEvent.obtain(now, now,
MotionEvent.ACTION_DOWN, me.getX(), me.getY(), me
.getMetaState());
result = onModifiedTouchEvent(down, false);
down.recycle();
if (action == MotionEvent.ACTION_UP) {
result = onModifiedTouchEvent(me, true);
}
} else {
MotionEvent up = MotionEvent.obtain(now, now,
MotionEvent.ACTION_UP, mOldPointerX, mOldPointerY, me
.getMetaState());
result = onModifiedTouchEvent(up, true);
up.recycle();
}
} else {
if (pointerCount == 1) {
result = onModifiedTouchEvent(me, false);
mOldPointerX = me.getX();
mOldPointerY = me.getY();
} else {
result = true;
}
}
mOldPointerCount = pointerCount;
return result;
}
private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
int touchX = (int) me.getX() - getPaddingLeft();
int touchY = (int) me.getY() + mVerticalCorrection - getPaddingTop();
final int action = me.getAction();
final long eventTime = me.getEventTime();
mOldEventTime = eventTime;
int keyIndex = getKeyIndices(touchX, touchY, null);
mPossiblePoly = possiblePoly;
if (action == MotionEvent.ACTION_DOWN)
mSwipeTracker.clear();
mSwipeTracker.addMovement(me);
if (mGestureDetector.onTouchEvent(me)) {
showPreview(NOT_A_KEY);
mHandler.removeMessages(MSG_REPEAT);
mHandler.removeMessages(MSG_LONGPRESS);
return true;
}
if (mMiniKeyboardOnScreen) {
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mAbortKey = false;
mStartX = touchX;
mStartY = touchY;
mLastCodeX = touchX;
mLastCodeY = touchY;
mLastKeyTime = 0;
mCurrentKeyTime = 0;
mLastKey = NOT_A_KEY;
mCurrentKey = keyIndex;
mDownKey = keyIndex;
mDownTime = me.getEventTime();
mLastMoveTime = mDownTime;
checkMultiTap(eventTime, keyIndex);
mKeyboardActionListener
.onPress(keyIndex != NOT_A_KEY ? mKeys[keyIndex].codes[0]
: 0);
if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
mRepeatKeyIndex = mCurrentKey;
repeatKey();
Message msg = mHandler.obtainMessage(MSG_REPEAT);
mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
}
if (mCurrentKey != NOT_A_KEY) {
Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
}
showPreview(keyIndex);
break;
case MotionEvent.ACTION_MOVE:
boolean continueLongPress = false;
if (keyIndex != NOT_A_KEY) {
if (mCurrentKey == NOT_A_KEY) {
mCurrentKey = keyIndex;
mCurrentKeyTime = eventTime - mDownTime;
} else {
if (keyIndex == mCurrentKey) {
mCurrentKeyTime += eventTime - mLastMoveTime;
continueLongPress = true;
} else if (mRepeatKeyIndex == NOT_A_KEY) {
resetMultiTap();
mLastKey = mCurrentKey;
mLastCodeX = mLastX;
mLastCodeY = mLastY;
mLastKeyTime = mCurrentKeyTime + eventTime
- mLastMoveTime;
mCurrentKey = keyIndex;
mCurrentKeyTime = 0;
}
}
}
if (!continueLongPress) {
mHandler.removeMessages(MSG_LONGPRESS);
if (keyIndex != NOT_A_KEY) {
Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
}
}
showPreview(mCurrentKey);
break;
case MotionEvent.ACTION_UP:
removeMessages();
if (keyIndex == mCurrentKey) {
mCurrentKeyTime += eventTime - mLastMoveTime;
} else {
resetMultiTap();
mLastKey = mCurrentKey;
mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
mCurrentKey = keyIndex;
mCurrentKeyTime = 0;
}
if (mCurrentKeyTime < mLastKeyTime && mLastKey != NOT_A_KEY) {
mCurrentKey = mLastKey;
touchX = mLastCodeX;
touchY = mLastCodeY;
}
showPreview(NOT_A_KEY);
Arrays.fill(mKeyIndices, NOT_A_KEY);
if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen
&& !mAbortKey) {
detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
}
invalidateKey(keyIndex);
mRepeatKeyIndex = NOT_A_KEY;
break;
case MotionEvent.ACTION_CANCEL:
removeMessages();
mAbortKey = true;
showPreview(NOT_A_KEY);
invalidateKey(mCurrentKey);
break;
}
mLastX = touchX;
mLastY = touchY;
return true;
}
private boolean repeatKey() {
Key key = mKeys[mRepeatKeyIndex];
detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
return true;
}
private void removeMessages() {
mHandler.removeMessages(MSG_REPEAT);
mHandler.removeMessages(MSG_LONGPRESS);
mHandler.removeMessages(MSG_SHOW_PREVIEW);
}
private void dismissPopupKeyboard() {
if (mPopupKeyboard.isShowing()) {
mPopupKeyboard.dismiss();
mMiniKeyboardOnScreen = false;
invalidateAllKeys();
}
}
public boolean handleBack() {
if (mPopupKeyboard.isShowing()) {
dismissPopupKeyboard();
return true;
}
return false;
}
private void resetMultiTap() {
mLastSentIndex = NOT_A_KEY;
mTapCount = 0;
mLastTapTime = -1;
mInMultiTap = false;
}
private void checkMultiTap(long eventTime, int keyIndex) {
if (keyIndex == NOT_A_KEY)
return;
Key key = mKeys[keyIndex];
if (key.codes.length > 1) {
mInMultiTap = true;
if (eventTime < mLastTapTime + MULTITAP_INTERVAL
&& keyIndex == mLastSentIndex) {
mTapCount = (mTapCount + 1) % key.codes.length;
return;
} else {
mTapCount = -1;
return;
}
}
if (eventTime > mLastTapTime + MULTITAP_INTERVAL
|| keyIndex != mLastSentIndex) {
resetMultiTap();
}
}
private static class SwipeTracker {
static final int NUM_PAST = 4;
static final int LONGEST_PAST_TIME = 200;
final float mPastX[] = new float[NUM_PAST];
final float mPastY[] = new float[NUM_PAST];
final long mPastTime[] = new long[NUM_PAST];
float mYVelocity;
float mXVelocity;
public void clear() {
mPastTime[0] = 0;
}
public void addMovement(MotionEvent ev) {
long time = ev.getEventTime();
final int N = ev.getHistorySize();
for (int i = 0; i < N; i++) {
addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev
.getHistoricalEventTime(i));
}
addPoint(ev.getX(), ev.getY(), time);
}
private void addPoint(float x, float y, long time) {
int drop = -1;
int i;
final long[] pastTime = mPastTime;
for (i = 0; i < NUM_PAST; i++) {
if (pastTime[i] == 0) {
break;
} else if (pastTime[i] < time - LONGEST_PAST_TIME) {
drop = i;
}
}
if (i == NUM_PAST && drop < 0) {
drop = 0;
}
if (drop == i)
drop--;
final float[] pastX = mPastX;
final float[] pastY = mPastY;
if (drop >= 0) {
final int start = drop + 1;
final int count = NUM_PAST - drop - 1;
System.arraycopy(pastX, start, pastX, 0, count);
System.arraycopy(pastY, start, pastY, 0, count);
System.arraycopy(pastTime, start, pastTime, 0, count);
i -= (drop + 1);
}
pastX[i] = x;
pastY[i] = y;
pastTime[i] = time;
i++;
if (i < NUM_PAST) {
pastTime[i] = 0;
}
}
public void computeCurrentVelocity(int units) {
computeCurrentVelocity(units, Float.MAX_VALUE);
}
public void computeCurrentVelocity(int units, float maxVelocity) {
final float[] pastX = mPastX;
final float[] pastY = mPastY;
final long[] pastTime = mPastTime;
final float oldestX = pastX[0];
final float oldestY = pastY[0];
final long oldestTime = pastTime[0];
float accumX = 0;
float accumY = 0;
int N = 0;
while (N < NUM_PAST) {
if (pastTime[N] == 0) {
break;
}
N++;
}
for (int i = 1; i < N; i++) {
final int dur = (int) (pastTime[i] - oldestTime);
if (dur == 0)
continue;
float dist = pastX[i] - oldestX;
float vel = (dist / dur) * units;
if ((int) accumX == 0)
accumX = vel;
else
accumX = (accumX + vel) * .5f;
dist = pastY[i] - oldestY;
vel = (dist / dur) * units;
if ((int) accumY == 0)
accumY = vel;
else
accumY = (accumY + vel) * .5f;
}
mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math
.min(accumX, maxVelocity);
mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math
.min(accumY, maxVelocity);
}
public float getXVelocity() {
return mXVelocity;
}
public float getYVelocity() {
return mYVelocity;
}
}
public void init() {
mCurrentRow = 0;
mCurrentColumn = 0;
mCurrentKey = 0;
setShifted(true);
// mCurrentKeyIndex = 0;
showPreview(mCurrentKey);
}
}
MTKKeyboard.java
java
public class MTKKeyboard {
static final String TAG = "Keyboard";
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_DELETE = -5;
private int mDefaultWidth;
private int mDefaultHeight;
private int mDefaultHorizontalGap;
private int mDefaultVerticalGap;
private boolean mShifted;
private Key mShiftKey;
private int mShiftKeyIndex = -1;
private int mTotalHeight;
private int mTotalWidth;
private List<Key> mKeys;
private List<Key> mModifierKeys;
private int mDisplayWidth;
private int mDisplayHeight;
private int mKeyboardMode;
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;
private int mRowCount;
private int[] mColumnCount;
private static float SEARCH_DISTANCE = 1.8f;
public static class Row {
public int defaultWidth;
public int defaultHeight;
public int defaultHorizontalGap;
public int verticalGap;
public int rowEdgeFlags;
public int mode;
private MTKKeyboard parent;
public Row(MTKKeyboard parent) {
this.parent = parent;
}
public Row(Resources res, MTKKeyboard parent, XmlResourceParser parser) {
this.parent = parent;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
defaultWidth = getDimensionOrFraction(a,
R.styleable.Keyboard_keyWidth, parent.mDisplayWidth,
parent.mDefaultWidth);
defaultHeight = getDimensionOrFraction(a,
R.styleable.Keyboard_keyHeight, parent.mDisplayHeight,
parent.mDefaultHeight);
defaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.Keyboard_horizontalGap, parent.mDisplayWidth,
parent.mDefaultHorizontalGap);
verticalGap = getDimensionOrFraction(a,
R.styleable.Keyboard_verticalGap, parent.mDisplayHeight,
parent.mDefaultVerticalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Row);
rowEdgeFlags = a.getInt(R.styleable.Keyboard_Row_rowEdgeFlags, 0);
mode = a.getResourceId(R.styleable.Keyboard_Row_keyboardMode, 0);
}
}
public static class Key {
public int[] codes;
public CharSequence label;
public Drawable icon;
public int width;
public int height;
public int gap;
public boolean sticky;
public int x;
public int y;
public boolean pressed;
public boolean on;
public CharSequence text;
public int edgeFlags;
public boolean modifier;
private MTKKeyboard keyboard;
public boolean repeatable;
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};
public Key(Row parent) {
keyboard = parent.parent;
}
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),
R.styleable.Keyboard);
width = getDimensionOrFraction(a, R.styleable.Keyboard_keyWidth,
keyboard.mDisplayWidth, parent.defaultWidth);
height = getDimensionOrFraction(a, R.styleable.Keyboard_keyHeight,
keyboard.mDisplayHeight, parent.defaultHeight);
gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap,
keyboard.mDisplayWidth, parent.defaultHorizontalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
this.x += gap;
TypedValue codesValue = new TypedValue();
a.getValue(R.styleable.Keyboard_Key_codes, codesValue);
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());
}
repeatable = a.getBoolean(R.styleable.Keyboard_Key_isRepeatable,
false);
modifier = a.getBoolean(R.styleable.Keyboard_Key_isModifier, false);
sticky = a.getBoolean(R.styleable.Keyboard_Key_isSticky, false);
edgeFlags = a.getInt(R.styleable.Keyboard_Key_keyEdgeFlags, 0);
edgeFlags |= parent.rowEdgeFlags;
icon = a.getDrawable(R.styleable.Keyboard_Key_keyIcon);
if (icon != null) {
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon
.getIntrinsicHeight());
}
label = a.getText(R.styleable.Keyboard_Key_keyLabel);
text = a.getText(R.styleable.Keyboard_Key_keyOutputText);
if (codes == null && !TextUtils.isEmpty(label)) {
codes = new int[]{label.charAt(0)};
}
a.recycle();
}
public void onPressed() {
pressed = !pressed;
}
public void onReleased(boolean inside) {
pressed = !pressed;
if (sticky) {
on = !on;
}
}
int[] parseCSV(String value) {
int count = 0;
int lastIndex = 0;
if (value.length() > 0) {
count++;
while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
count++;
}
}
int[] values = new int[count];
count = 0;
StringTokenizer st = new StringTokenizer(value, ",");
while (st.hasMoreTokens()) {
try {
values[count++] = Integer.parseInt(st.nextToken());
} catch (NumberFormatException nfe) {
MtkLog.e(TAG, "Error parsing keycodes " + value);
}
}
return values;
}
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;
}
}
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;
}
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;
}
}
public MTKKeyboard(Context context, int xmlLayoutResId) {
this(context, xmlLayoutResId, 0);
}
public MTKKeyboard(Context context, int xmlLayoutResId, int parentW,
int parentH) {
mDisplayWidth = parentW;
mDisplayHeight = parentH;
mDefaultWidth = mDisplayWidth / 10;
mDefaultHeight = mDefaultWidth;
mDefaultHorizontalGap = 0;
mDefaultVerticalGap = 0;
mKeys = new ArrayList<Key>();
mModifierKeys = new ArrayList<Key>();
mKeyboardMode = 0;
loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
}
public MTKKeyboard(Context context, int xmlLayoutResId, int modeId) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mDisplayWidth = dm.widthPixels;
mDisplayHeight = dm.heightPixels;
MtkLog.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));
}
public MTKKeyboard(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.width = mDefaultWidth;
key.height = mDefaultHeight;
key.gap = mDefaultHorizontalGap;
key.label = String.valueOf(c);
key.codes = new int[]{c};
column++;
x += key.width + key.gap;
mKeys.add(key);
if (x > mTotalWidth) {
mTotalWidth = x;
}
}
mTotalHeight = y + mDefaultHeight;
}
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;
}
public int getHeight() {
return mTotalHeight;
}
public int getMinWidth() {
return mTotalWidth;
}
public boolean setShifted(boolean shiftState) {
if (mShiftKey != null) {
mShiftKey.on = shiftState;
}
if (mShifted != shiftState) {
mShifted = shiftState;
return true;
}
return false;
}
public boolean isShifted() {
return mShifted;
}
public int getShiftKeyIndex() {
return mShiftKeyIndex;
}
private void computeNearestNeighbors() {
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;
}
}
}
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;
int x = 0;
int y = 0;
Key key = null;
Row currentRow = null;
Resources res = context.getResources();
boolean skipRow = false;
int[] columnCount = new int[10];
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);
skipRow = currentRow.mode != 0
&& currentRow.mode != mKeyboardMode;
if (skipRow) {
skipToEndOfRow(parser);
inRow = false;
}
} else if (TAG_KEY.equals(tag)) {
inKey = true;
if (currentRow != null) {
key = createKeyFromXml(res, currentRow, x, y, parser);
}
if (key != null && mKeys.size() == 0) {
key.onPressed();
}
mKeys.add(key);
if (key != null && key.codes[0] == KEYCODE_SHIFT) {
mShiftKey = key;
mShiftKeyIndex = mKeys.size() - 1;
mModifierKeys.add(key);
}
} else if (TAG_KEYBOARD.equals(tag)) {
parseKeyboardAttributes(res, parser);
}
} else if (event == XmlResourceParser.END_TAG) {
if (inKey) {
inKey = false;
if (null != key) {
x += key.gap + key.width;
}
if (x > mTotalWidth) {
mTotalWidth = x;
}
columnCount[mRowCount]++;
} else if (inRow) {
inRow = false;
y += currentRow.verticalGap;
y += currentRow.defaultHeight;
mRowCount++;
} else {
}
}
}
mColumnCount = columnCount;
} catch (Exception e) {
MtkLog.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),
R.styleable.Keyboard);
mDefaultWidth = getDimensionOrFraction(a,
R.styleable.Keyboard_keyWidth, mDisplayWidth,
mDisplayWidth / 10);
mDefaultHeight = getDimensionOrFraction(a,
R.styleable.Keyboard_keyHeight, mDisplayHeight, 50);
mDefaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.Keyboard_horizontalGap, mDisplayWidth, 0);
mDefaultVerticalGap = getDimensionOrFraction(a,
R.styleable.Keyboard_verticalGap, mDisplayHeight, 0);
mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
mProximityThreshold = mProximityThreshold * mProximityThreshold;
}
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) {
return Math.round(a.getFraction(index, base, base, defValue));
}
return defValue;
}
public void setRowCount(int mRowCount) {
this.mRowCount = mRowCount;
}
public int getRowCount() {
return mRowCount;
}
public void setColumnCount(int[] mColumnCount) {
this.mColumnCount = mColumnCount;
}
public int[] getColumnCount() {
return mColumnCount;
}
}
xml
<resources>
<declare-styleable name="TileView">
<attr name="tileSize" format="integer" />
</declare-styleable>
<declare-styleable name="Keyboard">
<attr name="keyWidth" format="dimension|fraction" />
<attr name="keyHeight" format="dimension|fraction" />
<attr name="horizontalGap" format="dimension|fraction" />
<attr name="verticalGap" format="dimension|fraction" />
</declare-styleable>
<declare-styleable name="Keyboard_Row">
<attr name="rowEdgeFlags">
<flag name="top" value="4" />
<flag name="bottom" value="8" />
</attr>
<attr name="keyboardMode" format="reference" />
</declare-styleable>
<declare-styleable name="Keyboard_Key">
<attr name="codes" format="integer|string" />
<attr name="popupKeyboard" format="reference" />
<attr name="popupCharacters" format="string" />
<attr name="keyEdgeFlags">
<flag name="left" value="1" />
<flag name="right" value="2" />
</attr>
<attr name="isModifier" format="boolean" />
<attr name="isSticky" format="boolean" />
<attr name="isRepeatable" format="boolean" />
<attr name="iconPreview" format="reference" />
<attr name="keyOutputText" format="string" />
<attr name="keyLabel" format="string" />
<attr name="keyIcon" format="reference" />
<attr name="keyboardMode" />
</declare-styleable>
<declare-styleable name="KeyboardView">
<attr name="keyboardViewStyle" format="reference" />
<attr name="keyBackground" format="reference" />
<attr name="keyTextSize" format="dimension" />
<attr name="labelTextSize" format="dimension" />
<attr name="keyTextColor" format="color" />
<attr name="keyPreviewLayout" format="reference" />
<attr name="keyPreviewOffset" format="dimension" />
<attr name="keyPreviewHeight" format="dimension" />
<attr name="verticalCorrection" format="dimension" />
<attr name="popupLayout" format="reference" />
<attr name="shadowColor" format="color" />
<attr name="shadowRadius" format="float" />
</declare-styleable>
<declare-styleable name="KeyboardViewPreviewState">
<attr name="state_long_pressable" format="boolean" />
</declare-styleable>
<declare-styleable name="Carousel">
<attr name="android:gravity" />
<attr name="android:animationDuration" />
<attr name="UseReflection" format="boolean"/>
<attr name="Items" format="integer"/>
<attr name="SelectedItem" format="integer"/>
<attr name="maxTheta" format="float"/>
<attr name="minQuantity" format="integer"/>
<attr name="maxQuantity" format="integer"/>
</declare-styleable>
</resources>
mmp_keyboard.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/com.mediatek.wwtv.mediaplayer"
android:keyWidth="10%p" android:keyHeight="12%p" android:horizontalGap="0px"
android:verticalGap="0px">
<Row android:rowEdgeFlags="top">
<Key android:codes="97" android:keyLabel="a" android:keyEdgeFlags="left" />
<Key android:codes="98" android:keyLabel="b" />
<Key android:codes="99" android:keyLabel="c" />
<Key android:codes="100" android:keyLabel="d" />
<Key android:codes="101" android:keyLabel="e" />
<Key android:codes="102" android:keyLabel="f" />
<Key android:codes="103" android:keyLabel="g" />
<Key android:codes="104" android:keyLabel="h" />
<Key android:codes="105" android:keyLabel="i"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="106" android:keyLabel="j"
android:keyEdgeFlags="left" />
<Key android:codes="107" android:keyLabel="k" />
<Key android:codes="108" android:keyLabel="l" />
<Key android:codes="109" android:keyLabel="m" />
<Key android:codes="110" android:keyLabel="n" />
<Key android:codes="111" android:keyLabel="o" />
<Key android:codes="112" android:keyLabel="p" />
<Key android:codes="113" android:keyLabel="q" />
<Key android:codes="114" android:keyLabel="r"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="115" android:keyLabel="s"
android:keyEdgeFlags="left" />
<Key android:codes="116" android:keyLabel="t" />
<Key android:codes="117" android:keyLabel="u" />
<Key android:codes="118" android:keyLabel="v" />
<Key android:codes="119" android:keyLabel="w" />
<Key android:codes="120" android:keyLabel="x" />
<Key android:codes="121" android:keyLabel="y" />
<Key android:codes="122" android:keyLabel="z" />
<Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left" />
<Key android:codes="50" android:keyLabel="2" />
<Key android:codes="51" android:keyLabel="3" />
<Key android:codes="52" android:keyLabel="4" />
<Key android:codes="53" android:keyLabel="5" />
<Key android:codes="54" android:keyLabel="6" />
<Key android:codes="55" android:keyLabel="7" />
<Key android:codes="56" android:keyLabel="8" />
<Key android:codes="57" android:keyLabel="9" android:keyEdgeFlags="right" />
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="46" android:keyLabel=". "
android:keyEdgeFlags="left" />
<Key android:codes="45" android:keyLabel="-" />
<Key android:codes="-1" android:keyLabel="Aa" android:isModifier="true"
android:isSticky="true" />
<!-- <Key android:codes="32" android:keyLabel="Space" android:keyWidth="50%p" />-->
<Key android:codes="32" android:keyLabel="Space" android:keyWidth="30%p"/>
<Key android:codes="33" android:keyLabel="!"/>
<Key android:codes="-5" android:keyLabel="<="
android:keyEdgeFlags="right" />
</Row>
</Keyboard>
KeyboardDialog.java
java
public class KeyboardDialog extends Dialog implements OnClickListener,
OnShowListener {
private static final String TAG = "KeyboardDialog";
private static final String SUFFIX = "_";
private static final String PWD_CHAR = "*";
private EditText vInput;
private Button vPosition;
private Button vNegative;
private MTKKeyboardView vKeyboardView;
private OnPressedListener mListener;
private boolean mPasswordMode;
private String mContent;
public KeyboardDialog(Context context, boolean cancelable,
OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public KeyboardDialog(Context context, int theme) {
super(context, theme);
}
public KeyboardDialog(Context context) {
this(context, R.style.dialog);
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mmp_keyboard_dialog);
WindowManager.LayoutParams lp = getWindow().getAttributes();
Context context = getContext();
lp.width = DisplayUtil.getWidthPixels(context, 0.30f);
lp.height = DisplayUtil.getHeightPixels(context, 0.5f);
getWindow().setAttributes(lp);
MTKKeyboard keyboard = new MTKKeyboard(getContext(),
R.xml.mmp_keyboard, lp.width, lp.height);
final EditText vEditText = (EditText) findViewById(R.id.mmp_input);
vEditText.setText("_");
vKeyboardView = (MTKKeyboardView) findViewById(R.id.keyboard);
vKeyboardView.setKeyboard(keyboard);
vKeyboardView.setShifted(true);
vKeyboardView.setFocusable(true);
vKeyboardView.requestFocus();
vKeyboardView
.setOnKeyboardActionListener(new OnKeyboardActionListener() {
public void onText(CharSequence text) {
}
public void onRelease(int primaryCode) {
}
public void onPress(int primaryCode) {
}
public void onKey(int primaryCode, int[] keyCodes) {
String input = null;
if (primaryCode == Keyboard.KEYCODE_SHIFT) {
vKeyboardView.setShifted(!vKeyboardView.isShifted());
} else if (primaryCode == Keyboard.KEYCODE_DELETE) {
int currentLength = mContent.length();
if (currentLength > 0) {
mContent = mContent.substring(0,
currentLength - 1);
if (mPasswordMode) {
input = vEditText.getText().toString()
.substring(1);
} else {
input = mContent + SUFFIX;
}
}
} else {
String key = String.valueOf((char) primaryCode);
if (vKeyboardView.isShifted()) {
key = key.toUpperCase();
}
mContent += key;
if (mPasswordMode) {
input = PWD_CHAR
+ vEditText.getText().toString();
} else {
input = mContent + SUFFIX;
}
}
if (input != null) {
vEditText.setText(input);
vEditText.setSelection(input.length() - 1);
}
}
});
vInput = (EditText) findViewById(R.id.mmp_input);
vPosition = (Button) findViewById(R.id.mmp_ok);
vNegative = (Button) findViewById(R.id.mmp_cancel);
vPosition.setOnClickListener(this);
vNegative.setOnClickListener(this);
setOnShowListener(this);
}
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyMap.KEYCODE_VOLUME_UP:
case KeyMap.KEYCODE_VOLUME_DOWN:
return true;
default:
break;
}
return super.dispatchKeyEvent(event);
}
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.mmp_ok:
if (mListener != null) {
mListener.onPositivePressed(getInput());
}
break;
case R.id.mmp_cancel:
if (mListener != null) {
mListener.onNegativePressed();
}
break;
default:
break;
}
}
public void setPrefill(String prefill) {
mContent = prefill;
MtkLog.d(TAG, "Set Content : " + mContent);
}
private String getInput() {
MtkLog.d(TAG, "Get Content : " + mContent);
return mContent;
}
public void isPassword(boolean isPassword) {
mPasswordMode = isPassword;
}
public void onShow(DialogInterface dialog) {
if (mContent != null) {
// String input = "";
StringBuilder inputBuilder = new StringBuilder("");
if (mPasswordMode) {
for (int i = 0; i < mContent.length(); i++) {
inputBuilder.append(PWD_CHAR);
//input += PWD_CHAR;
}
}
//else {
// input = mContent;
//}
vInput.setText(inputBuilder.toString() + SUFFIX);
vInput.setSelection(inputBuilder.length() + 1);
}
vKeyboardView.init();
vKeyboardView.requestFocus();
}
public void setOnPressedListener(OnPressedListener listener) {
mListener = listener;
}
public interface OnPressedListener {
void onPositivePressed(String input);
void onNegativePressed();
}
}
mmp_keyboard_dialog.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:mtk="http://schemas.android.com/apk/res/com.mediatek.wwtv.mediaplayer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/mm_bg"
android:gravity="center_horizontal"
android:orientation="vertical">
<EditText
android:id="@+id/mmp_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:focusable="false"
android:singleLine="true"
android:textSize="16sp" />
<com.mediatek.wwtv.mediaplayer.mmp.commonview.MTKKeyboardView
android:id="@+id/keyboard"
style="@style/Widget.KeyboardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusUp="@+id/mmp_ok"
android:nextFocusDown="@+id/mmp_ok" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dip"
android:gravity="center_horizontal">
<Button
android:id="@+id/mmp_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dip"
android:background="@drawable/menu_dialog_button"
android:nextFocusLeft="@+id/mmp_cancel"
android:nextFocusDown="@+id/keyboard"
android:text="@string/mmp_ok"
android:textColor="@android:color/white" />
<Button
android:id="@+id/mmp_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/menu_dialog_button"
android:nextFocusRight="@+id/mmp_ok"
android:nextFocusDown="@+id/keyboard"
android:text="@string/mmp_cancel"
android:textColor="@android:color/white" />
</LinearLayout>
</LinearLayout>