Android使用xml自定义软键盘效果(附源码)

来源:互联网 发布:怎样找到淘宝客服工作 编辑:程序博客网 时间:2024/06/05 22:49

Android使用xml自定义软键盘效果原理:
1,软键盘其实是个控件,使用android.inputmethodserver.KeyboardView类定义。
2,主布局中使用帧布局,当我们需要显示软键盘时设置为可见,不需要时设置为不可见。
3,编写xml文件,定义键盘内容。使用xml文件填充KeyBoardView布局
4,设置EditText的监听事件。
完成键盘开发。

上效果图:
这里写图片描述

1,源码研究android.inputmethodserver.KeyboardView:

/* * Copyright (C) 2008-2009 Google Inc. *  * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at *  * http://www.apache.org/licenses/LICENSE-2.0 *  * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */package android.inputmethodservice;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.Rect;import android.graphics.Typeface;import android.graphics.Paint.Align;import android.graphics.Region.Op;import android.graphics.drawable.Drawable;import android.inputmethodservice.Keyboard.Key;import android.media.AudioManager;import android.os.Handler;import android.os.Message;import android.provider.Settings;import android.util.AttributeSet;import android.util.TypedValue;import android.view.GestureDetector;import android.view.Gravity;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup.LayoutParams;import android.view.accessibility.AccessibilityEvent;import android.view.accessibility.AccessibilityManager;import android.widget.PopupWindow;import android.widget.TextView;import com.android.internal.R;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;/** * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and * detecting key presses and touch movements. *  * @attr ref android.R.styleable#KeyboardView_keyBackground * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset * @attr ref android.R.styleable#KeyboardView_labelTextSize * @attr ref android.R.styleable#KeyboardView_keyTextSize * @attr ref android.R.styleable#KeyboardView_keyTextColor * @attr ref android.R.styleable#KeyboardView_verticalCorrection * @attr ref android.R.styleable#KeyboardView_popupLayout */public class KeyboardView extends View implements View.OnClickListener {    /**     * Listener for virtual keyboard events.     */    public interface OnKeyboardActionListener {        /**         * Called when the user presses a key. This is sent before the {@link #onKey} is called.         * For keys that repeat, this is only called once.         * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid         * key, the value will be zero.         */        void onPress(int primaryCode);        /**         * Called when the user releases a key. This is sent after the {@link #onKey} is called.         * For keys that repeat, this is only called once.         * @param primaryCode the code of the key that was released         */        void onRelease(int primaryCode);        /**         * Send a key press to the listener.         * @param primaryCode this is the key that was pressed         * @param keyCodes the codes for all the possible alternative keys         * with the primary code being the first. If the primary key code is         * a single character such as an alphabet or number or symbol, the alternatives         * will include other characters that may be on the same key or adjacent keys.         * These codes are useful to correct for accidental presses of a key adjacent to         * the intended key.         */        void onKey(int primaryCode, int[] keyCodes);        /**         * Sends a sequence of characters to the listener.         * @param text the sequence of characters to be displayed.         */        void onText(CharSequence text);        /**         * Called when the user quickly moves the finger from right to left.         */        void swipeLeft();        /**         * Called when the user quickly moves the finger from left to right.         */        void swipeRight();        /**         * Called when the user quickly moves the finger from up to down.         */        void swipeDown();        /**         * Called when the user quickly moves the finger from down to up.         */        void swipeUp();    }    private static final boolean DEBUG = false;    private static final int NOT_A_KEY = -1;    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };    private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };       private Keyboard mKeyboard;    private int mCurrentKeyIndex = NOT_A_KEY;    private int mLabelTextSize;    private int mKeyTextSize;    private int mKeyTextColor;    private float mShadowRadius;    private int mShadowColor;    private float mBackgroundDimAmount;    private TextView mPreviewText;    private PopupWindow mPreviewPopup;    private int mPreviewTextSizeLarge;    private int mPreviewOffset;    private int mPreviewHeight;    // Working variable    private final int[] mCoordinates = new int[2];    private PopupWindow mPopupKeyboard;    private View mMiniKeyboardContainer;    private KeyboardView mMiniKeyboard;    private boolean mMiniKeyboardOnScreen;    private View mPopupParent;    private int mMiniKeyboardOffsetX;    private int mMiniKeyboardOffsetY;    private Map<Key,View> mMiniKeyboardCache;    private Key[] mKeys;    /** Listener for {@link OnKeyboardActionListener}. */    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 static final int DEBOUNCE_TIME = 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 = NOT_A_KEY;    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;    // Variables for dealing with multiple pointers    private int mOldPointerCount = 1;    private float mOldPointerX;    private float mOldPointerY;    private Drawable mKeyBackground;    private static final int REPEAT_INTERVAL = 50; // ~20 keys per second    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];    // For multi-tap    private int mLastSentIndex;    private int mTapCount;    private long mLastTapTime;    private boolean mInMultiTap;    private static final int MULTITAP_INTERVAL = 800; // milliseconds    private StringBuilder mPreviewLabel = new StringBuilder(1);    /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/    private boolean mDrawPending;    /** The dirty region in the keyboard bitmap */    private Rect mDirtyRect = new Rect();    /** The keyboard bitmap for faster updates */    private Bitmap mBuffer;    /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */    private boolean mKeyboardChanged;    /** The canvas for the above mutable keyboard bitmap */    private Canvas mCanvas;    /** The accessibility manager for accessibility support */    private AccessibilityManager mAccessibilityManager;    /** The audio manager for accessibility support */    private AudioManager mAudioManager;    /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */    private boolean mHeadsetRequiredToHearPasswordsAnnounced;    Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case MSG_SHOW_PREVIEW:                    showKey(msg.arg1);                    break;                case MSG_REMOVE_PREVIEW:                    mPreviewText.setVisibility(INVISIBLE);                    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;            }        }    };    public KeyboardView(Context context, AttributeSet attrs) {        this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);    }    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        TypedArray a =            context.obtainStyledAttributes(                attrs, android.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 com.android.internal.R.styleable.KeyboardView_keyBackground:                mKeyBackground = a.getDrawable(attr);                break;            case com.android.internal.R.styleable.KeyboardView_verticalCorrection:                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);                break;            case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:                previewLayout = a.getResourceId(attr, 0);                break;            case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);                break;            case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:                mPreviewHeight = a.getDimensionPixelSize(attr, 80);                break;            case com.android.internal.R.styleable.KeyboardView_keyTextSize:                mKeyTextSize = a.getDimensionPixelSize(attr, 18);                break;            case com.android.internal.R.styleable.KeyboardView_keyTextColor:                mKeyTextColor = a.getColor(attr, 0xFF000000);                break;            case com.android.internal.R.styleable.KeyboardView_labelTextSize:                mLabelTextSize = a.getDimensionPixelSize(attr, 14);                break;            case com.android.internal.R.styleable.KeyboardView_popupLayout:                mPopupLayout = a.getResourceId(attr, 0);                break;            case com.android.internal.R.styleable.KeyboardView_shadowColor:                mShadowColor = a.getColor(attr, 0);                break;            case com.android.internal.R.styleable.KeyboardView_shadowRadius:                mShadowRadius = a.getFloat(attr, 0f);                break;            }        }        a = mContext.obtainStyledAttributes(                com.android.internal.R.styleable.Theme);        mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);        mPreviewPopup = new PopupWindow(context);        if (previewLayout != 0) {            mPreviewText = (TextView) inflate.inflate(previewLayout, null);            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();            mPreviewPopup.setContentView(mPreviewText);            mPreviewPopup.setBackgroundDrawable(null);        } else {            mShowPreview = false;        }        mPreviewPopup.setTouchable(false);        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>();        mKeyBackground.getPadding(mPadding);        mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);        mDisambiguateSwipe = getResources().getBoolean(                com.android.internal.R.bool.config_swipeDisambiguation);        mAccessibilityManager = AccessibilityManager.getInstance(context);        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);        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; // Half the keyboard width                int travelY = getHeight() / 2; // Half the keyboard 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 {                        swipeRight();                        return true;                    }                } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {                    if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {                        sendDownKey = true;                    } else {                        swipeLeft();                        return true;                    }                } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {                    if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {                        sendDownKey = true;                    } else {                        swipeUp();                        return true;                    }                } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {                    if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {                        sendDownKey = true;                    } else {                        swipeDown();                        return true;                    }                }                if (sendDownKey) {                    detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());                }                return false;            }        });        mGestureDetector.setIsLongpressEnabled(false);    }    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {        mKeyboardActionListener = listener;    }    /**     * Returns the {@link OnKeyboardActionListener} object.     * @return the listener attached to this keyboard     */    protected OnKeyboardActionListener getOnKeyboardActionListener() {        return mKeyboardActionListener;    }    /**     * Attaches a keyboard to this view. The keyboard can be switched at any time and the     * view will re-layout itself to accommodate the keyboard.     * @see Keyboard     * @see #getKeyboard()     * @param keyboard the keyboard to display in this view     */    public void setKeyboard(Keyboard keyboard) {        if (mKeyboard != null) {            showPreview(NOT_A_KEY);        }        // Remove any pending messages        removeMessages();        mKeyboard = keyboard;        List<Key> keys = mKeyboard.getKeys();        mKeys = keys.toArray(new Key[keys.size()]);        requestLayout();        // Hint to reallocate the buffer if the size changed        mKeyboardChanged = true;        invalidateAllKeys();        computeProximityThreshold(keyboard);        mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views        // Switching to a different keyboard should abort any pending keys so that the key up        // doesn't get delivered to the old or new keyboard        mAbortKey = true; // Until the next ACTION_DOWN    }    /**     * Returns the current keyboard being displayed by this view.     * @return the currently attached keyboard     * @see #setKeyboard(Keyboard)     */    public Keyboard getKeyboard() {        return mKeyboard;    }    /**     * Sets the state of the shift key of the keyboard, if any.     * @param shifted whether or not to enable the state of the shift key     * @return true if the shift key state changed, false if there was no change     * @see KeyboardView#isShifted()     */    public boolean setShifted(boolean shifted) {        if (mKeyboard != null) {            if (mKeyboard.setShifted(shifted)) {                // The whole keyboard probably needs to be redrawn                invalidateAllKeys();                return true;            }        }        return false;    }    /**     * Returns the state of the shift key of the keyboard, if any.     * @return true if the shift is in a pressed state, false otherwise. If there is     * no shift key on the keyboard or there is no keyboard attached, it returns false.     * @see KeyboardView#setShifted(boolean)     */    public boolean isShifted() {        if (mKeyboard != null) {            return mKeyboard.isShifted();        }        return false;    }    /**     * Enables or disables the key feedback popup. This is a popup that shows a magnified     * version of the depressed key. By default the preview is enabled.      * @param previewEnabled whether or not to enable the key feedback popup     * @see #isPreviewEnabled()     */    public void setPreviewEnabled(boolean previewEnabled) {        mShowPreview = previewEnabled;    }    /**     * Returns the enabled state of the key feedback popup.     * @return whether or not the key feedback popup is enabled     * @see #setPreviewEnabled(boolean)     */    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;        if (mPreviewPopup.isShowing()) {            mPreviewPopup.dismiss();        }    }    /**     * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key     * codes for adjacent keys.  When disabled, only the primary key code will be     * reported.     * @param enabled whether or not the proximity correction is enabled     */    public void setProximityCorrectionEnabled(boolean enabled) {        mProximityCorrectOn = enabled;    }    /**     * Returns true if proximity correction is enabled.     */    public boolean isProximityCorrectionEnabled() {        return mProximityCorrectOn;    }    /**      * Popup keyboard close button clicked.     * @hide      */    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) {        // Round up a little        if (mKeyboard == null) {            setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);        } else {            int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {                width = MeasureSpec.getSize(widthMeasureSpec);            }            setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);        }    }    /**     * Compute the average distance between adjacent keys (horizontally and vertically)     * and square it to get the proximity threshold. We use a square here and in computing     * the touch distance from a key's center to avoid taking a square root.     * @param keyboard     */    private void computeProximityThreshold(Keyboard 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; // Square it    }    @Override    public void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        if (mKeyboard != null) {            mKeyboard.resize(w, h);        }        // Release the buffer, if any and it will be reallocated on the next draw        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())) {                // Make sure our bitmap is at least 1x1                final int width = Math.max(1, getWidth());                final int height = Math.max(1, getHeight());                mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);                mCanvas = new Canvas(mBuffer);            }            invalidateAllKeys();            mKeyboardChanged = false;        }        final Canvas canvas = mCanvas;        canvas.clipRect(mDirtyRect, Op.REPLACE);        if (mKeyboard == null) return;        final Paint paint = mPaint;        final Drawable keyBackground = mKeyBackground;        final Rect clipRegion = mClipRegion;        final Rect padding = mPadding;        final int kbdPaddingLeft = mPaddingLeft;        final int kbdPaddingTop = mPaddingTop;        final Key[] keys = mKeys;        final Key invalidKey = mInvalidatedKey;        paint.setColor(mKeyTextColor);        boolean drawSingleKey = false;        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {          // Is clipRegion completely contained within the invalidated key?          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 != key) {                continue;            }            int[] drawableState = key.getCurrentDrawableState();            keyBackground.setState(drawableState);            // Switch the character to uppercase if shift is pressed            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) {                // For characters, use large font. For labels like "Done", use small font.                if (label.length() > 1 && key.codes.length < 2) {                    paint.setTextSize(mLabelTextSize);                    paint.setTypeface(Typeface.DEFAULT_BOLD);                } else {                    paint.setTextSize(mKeyTextSize);                    paint.setTypeface(Typeface.DEFAULT);                }                // Draw a drop shadow for the text                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);                // Draw the text                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);                // Turn off drop shadow                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;        // Overlay a dark rectangle to dim the keyboard        if (mMiniKeyboardOnScreen) {            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);        }        if (DEBUG && mShowTouchPoints) {            paint.setAlpha(128);            paint.setColor(0xFFFF0000);            canvas.drawCircle(mStartX, mStartY, 3, paint);            canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);            paint.setColor(0xFF0000FF);            canvas.drawCircle(mLastX, mLastY, 3, paint);            paint.setColor(0xFF00FF00);            canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, 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 (isInside) {                primaryIndex = nearestKeyIndices[i];            }            if (((mProximityCorrectOn                     && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)                     || isInside)                    && key.codes[0] > 32) {                // Find insertion point                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) {                        // Make space for nCodes codes                        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 (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);                // Multi-tap                if (mInMultiTap) {                    if (mTapCount != -1) {                        mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);                    } else {                        mTapCount = 0;                    }                    code = key.codes[mTapCount];                }                mKeyboardActionListener.onKey(code, codes);                mKeyboardActionListener.onRelease(code);            }            mLastSentIndex = index;            mLastTapTime = eventTime;        }    }    /**     * Handle multi-tap keys by producing the key label for the current multi-tap state.     */    private CharSequence getPreviewText(Key key) {        if (mInMultiTap) {            // Multi-tap            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;        final PopupWindow previewPopup = mPreviewPopup;        mCurrentKeyIndex = keyIndex;        // Release the old key and press the new key        final Key[] keys = mKeys;        if (oldKeyIndex != mCurrentKeyIndex) {            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {                Key oldKey = keys[oldKeyIndex];                oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);                invalidateKey(oldKeyIndex);                sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,                        oldKey.codes[0]);            }            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {                Key newKey = keys[mCurrentKeyIndex];                newKey.onPressed();                invalidateKey(mCurrentKeyIndex);                sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,                        newKey.codes[0]);            }        }        // If key changed and preview is on ...        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {            mHandler.removeMessages(MSG_SHOW_PREVIEW);            if (previewPopup.isShowing()) {                if (keyIndex == NOT_A_KEY) {                    mHandler.sendMessageDelayed(mHandler                            .obtainMessage(MSG_REMOVE_PREVIEW),                             DELAY_AFTER_PREVIEW);                }            }            if (keyIndex != NOT_A_KEY) {                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {                    // Show right away, if it's already visible and finger is moving around                    showKey(keyIndex);                } else {                    mHandler.sendMessageDelayed(                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),                             DELAY_BEFORE_PREVIEW);                }            }        }    }    private void showKey(final int keyIndex) {        final PopupWindow previewPopup = mPreviewPopup;        final Key[] keys = mKeys;        if (keyIndex < 0 || keyIndex >= mKeys.length) return;        Key key = keys[keyIndex];        if (key.icon != null) {            mPreviewText.setCompoundDrawables(null, null, null,                     key.iconPreview != null ? key.iconPreview : key.icon);            mPreviewText.setText(null);        } else {            mPreviewText.setCompoundDrawables(null, null, null, null);            mPreviewText.setText(getPreviewText(key));            if (key.label.length() > 1 && key.codes.length < 2) {                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);            } else {                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);                mPreviewText.setTypeface(Typeface.DEFAULT);            }        }        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width                 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());        final int popupHeight = mPreviewHeight;        LayoutParams lp = mPreviewText.getLayoutParams();        if (lp != null) {            lp.width = popupWidth;            lp.height = popupHeight;        }        if (!mPreviewCentered) {            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;        } else {            // TODO: Fix this if centering is brought back            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;            mPopupPreviewY = - mPreviewText.getMeasuredHeight();        }        mHandler.removeMessages(MSG_REMOVE_PREVIEW);        getLocationInWindow(mCoordinates);        mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero        mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero        // Set the preview background state        mPreviewText.getBackground().setState(                key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);        mPopupPreviewX += mCoordinates[0];        mPopupPreviewY += mCoordinates[1];        // If the popup cannot be shown above the key, put it on the side        getLocationOnScreen(mCoordinates);        if (mPopupPreviewY + mCoordinates[1] < 0) {            // If the key you're pressing is on the left side of the keyboard, show the popup on            // the right, offset by enough to see at least one key to the left/right.            if (key.x + key.width <= getWidth() / 2) {                mPopupPreviewX += (int) (key.width * 2.5);            } else {                mPopupPreviewX -= (int) (key.width * 2.5);            }            mPopupPreviewY += popupHeight;        }        if (previewPopup.isShowing()) {            previewPopup.update(mPopupPreviewX, mPopupPreviewY,                    popupWidth, popupHeight);        } else {            previewPopup.setWidth(popupWidth);            previewPopup.setHeight(popupHeight);            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,                     mPopupPreviewX, mPopupPreviewY);        }        mPreviewText.setVisibility(VISIBLE);    }    private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {        if (mAccessibilityManager.isEnabled()) {            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);            onInitializeAccessibilityEvent(event);            String text = null;            // This is very efficient since the properties are cached.            final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),                    Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;            // Add text only if password announcement is enabled or if headset is            // used to avoid leaking passwords.            if (speakPassword || mAudioManager.isBluetoothA2dpOn()                    || mAudioManager.isWiredHeadsetOn()) {                switch (code) {                    case Keyboard.KEYCODE_ALT:                        text = mContext.getString(R.string.keyboardview_keycode_alt);                        break;                    case Keyboard.KEYCODE_CANCEL:                        text = mContext.getString(R.string.keyboardview_keycode_cancel);                        break;                    case Keyboard.KEYCODE_DELETE:                        text = mContext.getString(R.string.keyboardview_keycode_delete);                        break;                    case Keyboard.KEYCODE_DONE:                        text = mContext.getString(R.string.keyboardview_keycode_done);                        break;                    case Keyboard.KEYCODE_MODE_CHANGE:                        text = mContext.getString(R.string.keyboardview_keycode_mode_change);                        break;                    case Keyboard.KEYCODE_SHIFT:                        text = mContext.getString(R.string.keyboardview_keycode_shift);                        break;                    case '\n':                        text = mContext.getString(R.string.keyboardview_keycode_enter);                        break;                    default:                        text = String.valueOf((char) code);                }            } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {                // We want the waring for required head set to be send with both the                // hover enter and hover exit event, so set the flag after the exit.                if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {                    mHeadsetRequiredToHearPasswordsAnnounced = true;                }                text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);            } else {                text = mContext.getString(R.string.keyboard_password_character_no_headset);            }            event.getText().add(text);            mAccessibilityManager.sendAccessibilityEvent(event);        }    }    /**     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only      * draws the cached buffer.     * @see #invalidateKey(int)     */    public void invalidateAllKeys() {        mDirtyRect.union(0, 0, getWidth(), getHeight());        mDrawPending = true;        invalidate();    }    /**     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only     * one key is changing it's content. Any changes that affect the position or size of the key     * may not be honored.     * @param keyIndex the index of the key in the attached {@link Keyboard}.     * @see #invalidateAllKeys     */    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(key.x + mPaddingLeft, key.y + mPaddingTop,                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);        onBufferDraw();        invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);    }    private boolean openPopupIfRequired(MotionEvent me) {        // Check if we have a popup layout specified first.        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;    }    /**     * Called when a key is long pressed. By default this will open any popup keyboard associated     * with this key through the attributes popupLayout and popupCharacters.     * @param popupKey the key that was long pressed     * @return true if the long press is handled, false otherwise. Subclasses should call the     * method on the base class if the subclass doesn't wish to handle the call.     */    protected boolean onLongPress(Key popupKey) {        int popupKeyboardId = popupKey.popupResId;        if (popupKeyboardId != 0) {            mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);            if (mMiniKeyboardContainer == null) {                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(                        Context.LAYOUT_INFLATER_SERVICE);                mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(                        com.android.internal.R.id.keyboardView);                View closeButton = mMiniKeyboardContainer.findViewById(                        com.android.internal.R.id.closeButton);                if (closeButton != null) closeButton.setOnClickListener(this);                mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {                    public void onKey(int primaryCode, int[] keyCodes) {                        mKeyboardActionListener.onKey(primaryCode, keyCodes);                        dismissPopupKeyboard();                    }                    public void onText(CharSequence text) {                        mKeyboardActionListener.onText(text);                        dismissPopupKeyboard();                    }                    public void swipeLeft() { }                    public void swipeRight() { }                    public void swipeUp() { }                    public void swipeDown() { }                    public void onPress(int primaryCode) {                        mKeyboardActionListener.onPress(primaryCode);                    }                    public void onRelease(int primaryCode) {                        mKeyboardActionListener.onRelease(primaryCode);                    }                });                //mInputView.setSuggest(mSuggest);                Keyboard keyboard;                if (popupKey.popupCharacters != null) {                    keyboard = new Keyboard(getContext(), popupKeyboardId,                             popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());                } else {                    keyboard = new Keyboard(getContext(), popupKeyboardId);                }                mMiniKeyboard.setKeyboard(keyboard);                mMiniKeyboard.setPopupParent(this);                mMiniKeyboardContainer.measure(                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),                         MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));                mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);            } else {                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(                        com.android.internal.R.id.keyboardView);            }            getLocationInWindow(mCoordinates);            mPopupX = popupKey.x + mPaddingLeft;            mPopupY = popupKey.y + mPaddingTop;            mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();            mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();            final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];            final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[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;        }        return false;    }    @Override    public boolean onHoverEvent(MotionEvent event) {        if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {            final int action = event.getAction();            switch (action) {                case MotionEvent.ACTION_HOVER_ENTER:                case MotionEvent.ACTION_HOVER_MOVE:                    final int touchX = (int) event.getX() - mPaddingLeft;                    int touchY = (int) event.getY() - mPaddingTop;                    if (touchY >= -mVerticalCorrection) {                        touchY += mVerticalCorrection;                    }                    final int keyIndex = getKeyIndices(touchX, touchY, null);                    showPreview(keyIndex);                    break;                case MotionEvent.ACTION_HOVER_EXIT:                    showPreview(NOT_A_KEY);                    break;            }        }        return true;    }    @Override    public boolean onTouchEvent(MotionEvent me) {        // Convert multi-pointer up/down events to single up/down events to         // deal with the typical multi-pointer behavior of two-thumb typing        final int pointerCount = me.getPointerCount();        final int action = me.getAction();        boolean result = false;        final long now = me.getEventTime();        if (pointerCount != mOldPointerCount) {            if (pointerCount == 1) {                // Send a down event for the latest pointer                MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,                        me.getX(), me.getY(), me.getMetaState());                result = onModifiedTouchEvent(down, false);                down.recycle();                // If it's an up action, then deliver the up as well.                if (action == MotionEvent.ACTION_UP) {                    result = onModifiedTouchEvent(me, true);                }            } else {                // Send an up event for the last pointer                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 {                // Don't do anything when 2 pointers are down and moving.                result = true;            }        }        mOldPointerCount = pointerCount;        return result;    }    private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {        int touchX = (int) me.getX() - mPaddingLeft;        int touchY = (int) me.getY() - mPaddingTop;        if (touchY >= -mVerticalCorrection)            touchY += mVerticalCorrection;        final int action = me.getAction();        final long eventTime = me.getEventTime();        int keyIndex = getKeyIndices(touchX, touchY, null);        mPossiblePoly = possiblePoly;        // Track the last few movements to look for spurious swipes.        if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();        mSwipeTracker.addMovement(me);        // Ignore all motion events until a DOWN.        if (mAbortKey                && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {            return true;        }        if (mGestureDetector.onTouchEvent(me)) {            showPreview(NOT_A_KEY);            mHandler.removeMessages(MSG_REPEAT);            mHandler.removeMessages(MSG_LONGPRESS);            return true;        }        // Needs to be called after the gesture detector gets a turn, as it may have        // displayed the mini keyboard        if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {            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;                    Message msg = mHandler.obtainMessage(MSG_REPEAT);                    mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);                    repeatKey();                    // Delivering the key could have caused an abort                    if (mAbortKey) {                        mRepeatKeyIndex = NOT_A_KEY;                        break;                    }                }                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) {                    // Cancel old longpress                    mHandler.removeMessages(MSG_LONGPRESS);                    // Start new longpress if key has changed                    if (keyIndex != NOT_A_KEY) {                        Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);                        mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);                    }                }                showPreview(mCurrentKey);                mLastMoveTime = eventTime;                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 && mCurrentKeyTime < DEBOUNCE_TIME                        && mLastKey != NOT_A_KEY) {                    mCurrentKey = mLastKey;                    touchX = mLastCodeX;                    touchY = mLastCodeY;                }                showPreview(NOT_A_KEY);                Arrays.fill(mKeyIndices, NOT_A_KEY);                // If we're not on a repeating key (which sends on a DOWN event)                if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {                    detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);                }                invalidateKey(keyIndex);                mRepeatKeyIndex = NOT_A_KEY;                break;            case MotionEvent.ACTION_CANCEL:                removeMessages();                dismissPopupKeyboard();                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;    }    protected void swipeRight() {        mKeyboardActionListener.swipeRight();    }    protected void swipeLeft() {        mKeyboardActionListener.swipeLeft();    }    protected void swipeUp() {        mKeyboardActionListener.swipeUp();    }    protected void swipeDown() {        mKeyboardActionListener.swipeDown();    }    public void closing() {        if (mPreviewPopup.isShowing()) {            mPreviewPopup.dismiss();        }        removeMessages();        dismissPopupKeyboard();        mBuffer = null;        mCanvas = null;        mMiniKeyboardCache.clear();    }    private void removeMessages() {        mHandler.removeMessages(MSG_REPEAT);        mHandler.removeMessages(MSG_LONGPRESS);        mHandler.removeMessages(MSG_SHOW_PREVIEW);    }    @Override    public void onDetachedFromWindow() {        super.onDetachedFromWindow();        closing();    }    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;   // pixels/frame.                if (accumX == 0) accumX = vel;                else accumX = (accumX + vel) * .5f;                dist = pastY[i] - oldestY;                vel = (dist/dur) * units;   // pixels/frame.                if (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;        }    }}

2,定义主布局activity_main.xml界面:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"             xmlns:tools="http://schemas.android.com/tools"             android:layout_width="match_parent"             android:layout_height="match_parent"             tools:context=".MainActivity" >    <!--键盘是个  控件  我们需要时   填充布局进去  显示键盘 -->    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical"        >        <EditText            android:id="@+id/Etext_1"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="#00dfef"            android:layout_margin="20dp"            android:focusable="true"            android:focusableInTouchMode="true"            android:padding="10dp"            android:hint="触摸弹出自定义xml键盘(构造方法1)"            />        <EditText            android:id="@+id/Etext_2"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_margin="20dp"            android:padding="10dp"            android:hint="触摸弹出自定义xml键盘2(构造方法2)"            android:background="#00dfef"            />    </LinearLayout>    <!-- 是用xml自定义键盘样式   使用KeyboardView键盘控件存放定义的键盘布局 -->    <include        layout="@layout/key_board_view"        /></FrameLayout>

3,定义key_board_view布局其实是android.inputmethodserver.KeyboardView键盘控件。
不居中 背景颜色和 文本选择器不在赘述。读者根据需要自行添加。或者下载源码查看。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    >    <!-- 使用  android自带库中inputmethodserver包中 keyboardView 存放键盘布局 -->    <android.inputmethodservice.KeyboardView        android:id="@+id/my_keyboard_view"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:focusable="false"        android:focusableInTouchMode="true"        android:layout_alignParentBottom="true"        android:background="@color/keyboardbackground"        android:keyTextColor = "@color/keysTextColor"        android:keyBackground="@drawable/keys_board_selector"        android:visibility="gone"        /></RelativeLayout>

4,xml文件定义键盘内容:
分为数字键盘和英文键盘,参考电脑键盘。其code值为ASCII码值。

定义数字numskeys.xml键盘:

<?xml version="1.0" encoding="utf-8"?><Keyboard xmlns:android="http://schemas.android.com/apk/res/android"          android:keyWidth="25%p" android:horizontalGap="0px"          android:verticalGap="0px" android:keyHeight="45dip">    <Row>        <Key android:codes="49" android:keyLabel="1" />        <Key android:codes="50" android:keyLabel="2" />        <Key android:codes="51" android:keyLabel="3" />        <Key android:codes="57419" android:keyEdgeFlags="right"             android:keyIcon="@mipmap/sym_keyboard_left" />    </Row>    <Row>        <Key android:codes="52" android:keyLabel="4" />        <Key android:codes="53" android:keyLabel="5" />        <Key android:codes="54" android:keyLabel="6" />        <Key android:codes="57421" android:keyEdgeFlags="right"             android:keyIcon="@mipmap/sym_keyboard_right" />    </Row>    <Row>        <Key android:codes="55" android:keyLabel="7" />        <Key android:codes="56" android:keyLabel="8" />        <Key android:codes="57" android:keyLabel="9" />        <Key android:codes="-3" android:keyHeight="100dip"             android:keyEdgeFlags="right" android:isRepeatable="true"             android:keyLabel="done" />    </Row>    <Row>        <Key android:codes="-2" android:keyLabel="ABC" />        <Key android:codes="48" android:keyLabel="0" />        <Key android:codes="-5" android:keyIcon="@mipmap/sym_keyboard_delete" />    </Row></Keyboard>

定义英文wordskeys.xml键盘:

<?xml version="1.0" encoding="utf-8"?><Keyboard xmlns:android="http://schemas.android.com/apk/res/android"    android:keyWidth="10%p" android:keyHeight="45dip"    android:horizontalGap="1px" android:verticalGap="1px"    >    <Row>        <Key android:codes="113" android:keyEdgeFlags="left"             android:keyLabel="q"/>        <Key android:codes="119" android:keyEdgeFlags="left"             android:keyLabel="w"/>        <Key android:codes="101" android:keyEdgeFlags="left"             android:keyLabel="e"/>        <Key android:codes="114" android:keyEdgeFlags="left"             android:keyLabel="r"/>        <Key android:codes="116" android:keyEdgeFlags="left"             android:keyLabel="t"/>        <Key android:codes="121" android:keyEdgeFlags="left"             android:keyLabel="y"/>        <Key android:codes="117" android:keyEdgeFlags="left"             android:keyLabel="u"/>        <Key android:codes="105" android:keyEdgeFlags="left"             android:keyLabel="i"/>        <Key android:codes="111" android:keyEdgeFlags="left"             android:keyLabel="o"/>        <Key android:codes="112" android:keyEdgeFlags="left"             android:keyLabel="p"/>    </Row>    <Row>        <Key android:horizontalGap="4.999995%p" android:codes="97"             android:keyEdgeFlags="left" android:keyLabel="a" />        <Key android:codes="115" android:keyLabel="s" />        <Key android:codes="100" android:keyLabel="d" />        <Key android:codes="102" android:keyLabel="f" />        <Key android:codes="103" android:keyLabel="g" />        <Key android:codes="104" android:keyLabel="h" />        <Key android:codes="106" android:keyLabel="j" />        <Key android:codes="107" android:keyLabel="k" />        <Key android:codes="108" android:keyEdgeFlags="right"             android:keyLabel="l" />    </Row>    <Row>        <Key android:keyWidth="14.999998%p" android:codes="-1"             android:keyEdgeFlags="left" android:isModifier="true"             android:isSticky="true" android:keyIcon="@mipmap/sym_keyboard_shift" />        <Key android:codes="122" android:keyLabel="z" />        <Key android:codes="120" android:keyLabel="x" />        <Key android:codes="99" android:keyLabel="c" />        <Key android:codes="118" android:keyLabel="v" />        <Key android:codes="98" android:keyLabel="b" />        <Key android:codes="110" android:keyLabel="n" />        <Key android:codes="109" android:keyLabel="m" />        <Key android:keyWidth="14.999998%p" android:codes="-5"             android:keyEdgeFlags="right" android:isRepeatable="true"             android:keyIcon="@mipmap/sym_keyboard_delete" />    </Row>    <Row android:rowEdgeFlags="bottom">        <Key android:keyWidth="25%p" android:codes="-2"             android:keyLabel="12#" />        <Key android:keyWidth="14%p" android:codes="44"             android:keyLabel="," />        <Key android:keyWidth="25%p" android:codes="32"             android:isRepeatable="true" android:keyIcon="@mipmap/sym_keyboard_space" />        <Key android:keyWidth="14%p" android:codes="46"             android:keyLabel="." />        <Key android:keyWidth="25%p" android:codes="-3"             android:keyEdgeFlags="right" android:keyLabel="done" />    </Row></Keyboard>

自定义键盘类XMLKeysUtils.java:

package zhaoq.com.myxmlkeyboard;import android.app.Activity;import android.content.Context;import android.inputmethodservice.Keyboard;import android.inputmethodservice.KeyboardView;import android.text.Editable;import android.view.View;import android.widget.EditText;import java.util.List;/** * PACKAGE_NAME:zhaoq.com.myxmlkeyboard * CREATE_BY:zhaoqiang * AUTHOR_EMAIL:zhaoq_hero@163.com * DATE: 2016/04/13  18:03 * xml文件  定义的键盘 */public class XMlKeysUtils implements KeyboardView.OnKeyboardActionListener {    private EditText editText; //当前  需要添加内容的EditText    private Keyboard k1,k2;  //数字键盘 和字母键盘    private KeyboardView keyboardView;//存放键盘的  布局    //定义  构造方法一: 传入当前activity    public XMlKeysUtils(Activity activity,EditText editText){        this.editText = editText;        //获取 数字键盘 和英文键盘:        k1 = new Keyboard(activity.getApplicationContext(),R.xml.numskeys);        k2 = new Keyboard(activity.getApplicationContext(),R.xml.wordskeys);        keyboardView = (KeyboardView) activity.findViewById(R.id.my_keyboard_view);        //填充布局:        keyboardView.setKeyboard(k1);        keyboardView.setEnabled(true);        keyboardView.setPreviewEnabled(true);        keyboardView.setOnKeyboardActionListener(this);  //添加监听事件键盘的    }    //定义  构造方法二: 传入当前activity    public XMlKeysUtils(Context context,View view,EditText editText){        this.editText = editText;        //获取 数字键盘 和英文键盘:        k1 = new Keyboard(context,R.xml.numskeys);        k2 = new Keyboard(context,R.xml.wordskeys);        keyboardView = (KeyboardView) view.findViewById(R.id.my_keyboard_view);        //填充布局:        keyboardView.setKeyboard(k1);        keyboardView.setEnabled(true);        keyboardView.setPreviewEnabled(true);        keyboardView.setOnKeyboardActionListener(this);  //添加监听事件键盘的    }    //显示键盘    public void showKeyBoard(){        int visibility = keyboardView.getVisibility();        if(visibility == View.GONE || visibility == View.INVISIBLE){            keyboardView.setVisibility(View.VISIBLE);        }    }    //隐藏键盘:    public void hideKeyBoard(){        int visibility = keyboardView.getVisibility();        if(visibility == View.VISIBLE){            keyboardView.setVisibility(View.GONE);        }    }    private boolean isUpper = false;  //默认  为小写不是大写    //大小写切换    public void  changeKey(){        List<Keyboard.Key> keyList = k2.getKeys(); //获取字母键盘   所有字母        if(isUpper){ //是大写默认            isUpper = false;            for(Keyboard.Key key:keyList){                if(key.label!=null && isword(key.label.toString())){                    key.label = key.label.toString().toLowerCase();//转换为小写                    key.codes[0] =  key.codes[0] + 32; //转换为小写                }            }        }else{ //小写   转为大写            isUpper = true;            for(Keyboard.Key key:keyList){                if(key.label !=null && isword(key.label.toString())){                    key.label = key.label.toString().toUpperCase();//转换为大写                    key.codes[0] = key.codes[0] - 32;//                }            }        }    }    //判断  k2键盘中是否为英文:    private boolean isword(String str){        String wordstr = "abcdefghijklmnopqrstuvwxyz";        if(wordstr.indexOf(str.toLowerCase())>-1){            return  true;        }        return  false;    }    //------------------以下为键盘的监听事件--------------------    @Override    public void onPress(int primaryCode) {//按下事件    }    @Override    public void onRelease(int primaryCode) {//释放事件    }    @Override    public void onText(CharSequence text) {    }    @Override    public void swipeLeft() {    }    @Override    public void swipeRight() {    }    @Override    public void swipeDown() {    }    @Override    public void swipeUp() {    }    @Override    public void onKey(int primaryCode, int[] keyCodes) {        //添加监听事件:        Editable edittable = editText.getText();//获取   当前EditText的可编辑对象        int start = editText.getSelectionStart();//        if(primaryCode == Keyboard.KEYCODE_CANCEL){//完成  按键            hideKeyBoard();//隐藏键盘        }else if (primaryCode == Keyboard.KEYCODE_DELETE){//回退            if(edittable != null && edittable.length() > 0){                if(start>0){                    edittable.delete(start - 1,start);                }            }        }else if(primaryCode == Keyboard.KEYCODE_SHIFT){  //大小写  切换            changeKey();//切换大小写            keyboardView.setKeyboard(k2);//设置  为字母键盘        }else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE){//数字和英文键盘切换            if(isNum){//当前为   数字键盘                isNum = false;                keyboardView.setKeyboard(k2);// 设置为  字母键盘            }else{                isNum = true;                keyboardView.setKeyboard(k1);//设置为数字键盘            }        }else if(primaryCode == 57419){  //go left            if (start > 0) {                editText.setSelection(start - 1);            }        }else if(primaryCode == 57421){ //go right            if (start < editText.length()) {                editText.setSelection(start + 1);            }        }else{            edittable.insert(start,Character.toString((char)primaryCode));        }    }    private boolean isNum = false;//是否是数字键盘}

MainActivity.java 主界面:

package zhaoq.com.myxmlkeyboard;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.text.InputType;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.widget.EditText;/** * android  自定义键盘 * com.zhaoq.mykeyboard * @Email zhaoq_hero@163.com * @author zhaoQiang : 2016-4-13 */public class MainActivity extends AppCompatActivity implements View.OnTouchListener {    private EditText editText1,editText2;    private View view;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = LayoutInflater.from(this).inflate(R.layout.activity_main,null);        setContentView(view);        editText1 = (EditText) findViewById(R.id.Etext_1);        editText2 = (EditText) findViewById(R.id.Etext_2);        //添加 触摸事件:        editText2.setOnTouchListener(this);        editText1.setOnTouchListener(this);    }    // 添加  触摸显示键盘的事件    @Override    public boolean onTouch(View v, MotionEvent event) {        if(event.getAction()==MotionEvent.ACTION_UP){            editText1.setInputType(InputType.TYPE_NULL);            editText2.setInputType(InputType.TYPE_NULL);            switch (v.getId()){                case R.id.Etext_1:                    new XMlKeysUtils(this,editText1).showKeyBoard();                    break;                case R.id.Etext_2:                    new XMlKeysUtils(getApplicationContext(),view,editText2).showKeyBoard();                    break;                default:                    break;            }            return true;        }        return false;    }}

这就完成了我们自定义键盘的开发。

源码链接:(基于AndroidStudio开发)
CSDN:http://download.csdn.net/detail/u013233097/9490892
Github:https://github.com/229457269/MyXmlKeyBoard

1 0
原创粉丝点击