Android自定义输入键盘

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="&lt;="
			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>