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 variableprivate 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 pointersprivate int mOldPointerCount = 1;private float mOldPointerX;private float mOldPointerY;private Drawable mKeyBackground;private static final int REPEAT_INTERVAL = 50; // ~20 keys per secondprivate 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-tapprivate int mLastSentIndex;private int mTapCount;private long mLastTapTime;private boolean mInMultiTap;private static final int MULTITAP_INTERVAL = 800; // millisecondsprivate 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() {@Overridepublic 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() {@Overridepublic 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 widthint travelY = getHeight() / 2; // Half the keyboard heightmSwipeTracker.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 messagesremoveMessages();mKeyboard = keyboard;List<Key> keys = mKeyboard.getKeys();mKeys = keys.toArray(new Key[keys.size()]);requestLayout();// Hint to reallocate the buffer if the size changedmKeyboardChanged = 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 keyboardmAbortKey = 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 redrawninvalidateAllKeys();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;}@Overridepublic void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// Round up a littleif (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}@Overridepublic 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 drawmBuffer = null;}@Overridepublic 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 1x1final 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 pressedString 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 textpaint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);// Draw the textcanvas.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 shadowpaint.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 keyboardif (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 pointfinal 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 codesSystem.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-tapif (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-tapmPreviewLabel.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 keyfinal 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 aroundshowKey(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 backmPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;mPopupPreviewY = - mPreviewText.getMeasuredHeight();}mHandler.removeMessages(MSG_REMOVE_PREVIEW);getLocationInWindow(mCoordinates);mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zeromCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero// Set the preview background statemPreviewText.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 sidegetLocationOnScreen(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;}@Overridepublic 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;}@Overridepublic 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 typingfinal 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 pointerMotionEvent 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 pointerMotionEvent 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 keyboardif (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 abortif (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 longpressmHandler.removeMessages(MSG_LONGPRESS);// Start new longpress if key has changedif (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);}@Overridepublic 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; //当前  需要添加内容的EditTextprivate Keyboard k1,k2;  //数字键盘 和字母键盘private KeyboardView keyboardView;//存放键盘的  布局//定义  构造方法一: 传入当前activitypublic 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);  //添加监听事件键盘的}//定义  构造方法二: 传入当前activitypublic 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;}//------------------以下为键盘的监听事件--------------------@Overridepublic void onPress(int primaryCode) {//按下事件}@Overridepublic void onRelease(int primaryCode) {//释放事件}@Overridepublic void onText(CharSequence text) {}@Overridepublic void swipeLeft() {}@Overridepublic void swipeRight() {}@Overridepublic void swipeDown() {}@Overridepublic void swipeUp() {}@Overridepublic 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 leftif (start > 0) {editText.setSelection(start - 1);}}else if(primaryCode == 57421){ //go rightif (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;@Overrideprotected 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);}// 添加  触摸显示键盘的事件@Overridepublic 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

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

  1. VC++实现的软键盘功能(附源码)

      VC++开发常用功能一系列文章 (欢迎订阅,持续更新...) 软键盘界面如下: 置于桌面窗口最顶层: 置顶介绍: if (hwnd = FindWindow(NULL, szTitle)){// ...

  2. android水果忍者源码,android 水果忍者的 刀锋 效果实现源码

    [实例简介] android 水果忍者的 刀锋 效果实现源码 [实例截图] [核心代码] 54532水果忍者Ninjia └── Ninjia ├── AndroidManifest.xml ├── ...

  3. html仿微信滑动删除,使用Vue实现移动端左滑删除效果附源码

    左滑删除在移动端是很常见的一种操作,常见于删除购物车中的商品,删除收藏夹中文章等等场景.我们只需要手指按住要删除的对象,然后轻轻向左滑动,便会出现删除按钮,然后点击删除按钮即可删除对象. 点击下载源码 ...

  4. php jquery ajax登录,jQuery+Ajax+PHP弹出层异步登录效果(附源码下载)

    弹出层主要用于展示丰富的页面信息,还有一个更好的应用是弹出表单层丰富交互应用.常见的应用有弹出登录表单层,用户提交登录信息,后台验证登录成功后,弹出层消失,主页面局部刷新用户信息.本文我们将给大家介绍 ...

  5. Android简单、高性能的高斯模糊(毛玻璃)效果(附源码)

    毛玻璃效果相信很多朋友都眼红很久了, 隔壁ios系统对高斯模糊早就大范围使用了, 咱们Android却丝毫不为所动, 于是就只能靠广大开发者咯. 这是目前市面上性能最高的方案, 也不知道最初是哪位大神 ...

  6. CSS篇--水滴效果(附源码)

    效果展示 源码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UT ...

  7. ANDROID物联网开发从入门到实战附源码

    本书从获取源码和搭建应用开发环境开始讲起,依次讲解了基础知识篇.数据传输篇.信息识别篇.传感器应用篇和技术提高篇这 5大部分内容. 目录 第1篇 基础知识篇 第1章 Android系统介绍 2 1.1 ...

  8. Android 商城类应用实战之购物车附源码

    本文授权发布公众号[刘桂林],星球[Hi Android] 本文为实战类项目,所以陈述的逻辑为实现流程 + 核心代码,主要实现的还是购物车的动画与结算,首先我们来看下整体的效果图: 购物车一般都是后台 ...

  9. 【android-tips】如何在android应用中插入百度广告(附源码)

    (转载请注明出处:http://blog.csdn.net/buptgshengod) 1.介绍    现在游戏中的广告基本上已经成为了游戏创作者的一个重要的收入来源.其实插入广告还是挺简单的,本文选 ...

最新文章

  1. 初入数据科学领域,你需要有七个这样的思维
  2. Glow-流生成模型(一)
  3. systemctl常用命令
  4. 深度学习、机器学习与NLP的前世今生
  5. Python实现单向循环链表
  6. 使用git stash命令保存和恢复进度
  7. 运行java -version出错 Error: could not open `\lib\amd64\jvm.cfg‘
  8. 水平面:篡命铜钱の1
  9. js获取当前农历时间
  10. 技术交流:springboot配置阿里云日志服务与log4j2 lookup
  11. 做RAID和不做RAID的区别
  12. Dell Inspiron 5520 笔记本盲刷BIOS
  13. 电脑游戏业编年史之十二──叛逆
  14. 读《Python Algorithms: Mastering Basic Algorithms in the Python Language》
  15. 区块链论语:付币看真相是一个应用
  16. java 解析m3u8的实例_M3U8格式讲解及实际应用分析
  17. R语言R原生plot函数和lines函数的主要参数说明、解析(type、pch、cex、lty、lwd、col、xlab、ylab)
  18. 证书.p12导出 与 xxx.p1文件转出私钥,公钥,xxxx.crt文件,xxx.cre文件, xxx.pem文件
  19. 2021 CHITEC | InterSystems互联互通套件:六大能力助力医院互联互通建设
  20. AI模型风险评估 第1部分:动机

热门文章

  1. opencv的色彩空间
  2. Opencv获取电脑摄像头抓拍的信息,
  3. 如何开展系统安全测试
  4. 2021年中国家具制造业经营现状分析:营业收入达8004.6亿元,利润总额达433.7亿元[图]
  5. [bzoj3123][SDOI2013]森林
  6. Win32API大全
  7. 树莓派3B安装openwrt19.07.04
  8. 终于知道怎么看辐射3的地图了
  9. easyExcel给表格的每一列设置不同样式
  10. 服务器装机选哪个系统好,服务器该装08系统好还是03系统好?