diff --git a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VerificationCodeView.java b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VerificationCodeView.java index 175abd8..adf44de 100644 --- a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VerificationCodeView.java +++ b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VerificationCodeView.java @@ -14,7 +14,6 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.View; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; @@ -162,7 +161,7 @@ public VerificationCodeView(Context context, AttributeSet attrs) { @SuppressLint("ResourceAsColor") private void initView() { for (int i = 0; i < mEtNumber; i++) { - EditText editText = new EditText(mContext); + ZanyEditText editText = new ZanyEditText(mContext); initEditText(editText, i); addView(editText); if (i == 0) { //设置第一个editText获取焦点 @@ -171,7 +170,7 @@ private void initView() { } } - private void initEditText(EditText editText, int i) { + private void initEditText(ZanyEditText editText, int i) { int childHPadding = 14; int childVPadding = 14; @@ -281,7 +280,8 @@ public void afterTextChanged(Editable s) { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL) { + if (keyCode == KeyEvent.KEYCODE_DEL + && event.getAction() == KeyEvent.ACTION_DOWN) { backFocus(); } return false; @@ -301,10 +301,10 @@ public void setEnabled(boolean enabled) { */ private void focus() { int count = getChildCount(); - EditText editText; + ZanyEditText editText; //利用for循环找出还最前面那个还没被输入字符的EditText,并把焦点移交给它。 for (int i = 0; i < count; i++) { - editText = (EditText) getChildAt(i); + editText = (ZanyEditText) getChildAt(i); if (editText.getText().length() < 1) { editText.setCursorVisible(true); editText.requestFocus(); @@ -314,7 +314,7 @@ private void focus() { } } //强行最后一个一直拿焦点,不然删除时候出问题 - EditText lastEditText = (EditText) getChildAt(mEtNumber - 1); + ZanyEditText lastEditText = (ZanyEditText) getChildAt(mEtNumber - 1); if (!TextUtils.isEmpty(lastEditText.getText().toString())) { lastEditText.requestFocus(); } @@ -322,16 +322,16 @@ private void focus() { private void backFocus() { //博主手机不好,经常点一次却触发两次`onKey`事件,就设置了一个防止多点击,间隔100毫秒。 - long startTime = System.currentTimeMillis(); - EditText editText; + //long startTime = System.currentTimeMillis(); + ZanyEditText editText; //循环检测有字符的`editText`,把其置空,并获取焦点。 for (int i = mEtNumber - 1; i >= 0; i--) { - editText = (EditText) getChildAt(i); - if (editText.getText().length() >= 1 && startTime - endTime > 100) { + editText = (ZanyEditText) getChildAt(i); + if (editText.getText().length() >= 1) {// && startTime - endTime > 100 editText.setText(""); editText.setCursorVisible(true); editText.requestFocus(); - endTime = startTime; + //endTime = startTime; return; } } @@ -339,9 +339,9 @@ private void backFocus() { private void getResult() { StringBuffer stringBuffer = new StringBuffer(); - EditText editText; + ZanyEditText editText; for (int i = 0; i < mEtNumber; i++) { - editText = (EditText) getChildAt(i); + editText = (ZanyEditText) getChildAt(i); stringBuffer.append(editText.getText()); } if (onCodeFinishListener != null) { @@ -350,9 +350,9 @@ private void getResult() { } public void clearText() { - EditText editText; + ZanyEditText editText; for (int i = 0; i < mEtNumber; i++) { - editText = (EditText) getChildAt(i); + editText = (ZanyEditText) getChildAt(i); editText.setText(""); } focus(); diff --git a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/ZanyEditText.java b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/ZanyEditText.java new file mode 100644 index 0000000..627ace7 --- /dev/null +++ b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/ZanyEditText.java @@ -0,0 +1,83 @@ +package com.moziqi.pwd.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; +import android.widget.EditText; + +/** + * https://blog.csdn.net/yanghuiyu38/article/details/53638601 + */ +public class ZanyEditText extends EditText { + private OnDelKeyEventListener delKeyEventListener; + + public ZanyEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public ZanyEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ZanyEditText(Context context) { + super(context); + } + + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + + return new ZanyInputConnection(super.onCreateInputConnection(outAttrs), + true); + } + + private class ZanyInputConnection extends InputConnectionWrapper { + + public ZanyInputConnection(InputConnection target, boolean mutable) { + super(target, mutable); + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + if (delKeyEventListener != null) { + delKeyEventListener.onDeleteClick(); + return true; + } + } + return super.sendKeyEvent(event); + } + + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (beforeLength == 1 && afterLength == 0) { + return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_DEL)); + } + + return super.deleteSurroundingText(beforeLength, afterLength); + } + + } + + /** + * 功能描述:
+ * 〈功能详细描述〉 + * + * @param delKeyEventListener EditText删除回调 + */ + public void setDelKeyEventListener(OnDelKeyEventListener delKeyEventListener) { + this.delKeyEventListener = delKeyEventListener; + } + + public interface OnDelKeyEventListener { + void onDeleteClick(); + } +} \ No newline at end of file diff --git a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/DetectDelEventEditText.java b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/DetectDelEventEditText.java new file mode 100644 index 0000000..902963f --- /dev/null +++ b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/DetectDelEventEditText.java @@ -0,0 +1,92 @@ +package com.moziqi.pwd.widget.deledittext; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.EditText; + +/** + * https://blog.csdn.net/sollian/article/details/60959542 + */ +public class DetectDelEventEditText extends EditText implements View.OnKeyListener, + EditableInputConnection.OnDelEventListener { + private DelEventListener delEventListener; + + /** + * 防止delEvent触发两次。 + * 0:未初始化;1:使用onKey方法触发;2:使用onDelEvdent方法触发 + */ + private int flag; + + public DetectDelEventEditText(Context context) { + super(context); + init(); + } + + public DetectDelEventEditText(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DetectDelEventEditText(Context context, + @Nullable + AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setOnKeyListener(this); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + super.onCreateInputConnection(outAttrs); + EditableInputConnection editableInputConnection = new EditableInputConnection(this); + outAttrs.initialSelStart = getSelectionStart(); + outAttrs.initialSelEnd = getSelectionEnd(); + outAttrs.initialCapsMode = editableInputConnection.getCursorCapsMode(getInputType()); + + editableInputConnection.setDelEventListener(this); + flag = 0; + + return editableInputConnection; + } + + public void setDelListener(DelEventListener l) { + delEventListener = l; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (flag == 2) { + return false; + } + flag = 1; + return delEventListener != null && keyCode == KeyEvent.KEYCODE_DEL && event + .getAction() == KeyEvent.ACTION_DOWN && delEventListener.delEvent(); + } + + @Override + public boolean onDelEvent() { + if (flag == 1) { + return false; + } + flag = 2; + return delEventListener != null && delEventListener.delEvent(); + } + +// @Override +// public boolean onKey(View v, int keyCode, KeyEvent event) { +// return false; +// } + + public interface DelEventListener { + boolean delEvent(); + } +} \ No newline at end of file diff --git a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/EditableInputConnection.java b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/EditableInputConnection.java new file mode 100644 index 0000000..e854770 --- /dev/null +++ b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/EditableInputConnection.java @@ -0,0 +1,247 @@ +package com.moziqi.pwd.widget.deledittext; + +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.Spanned; +import android.text.method.KeyListener; +import android.text.style.SuggestionSpan; +import android.util.Log; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; + +public class EditableInputConnection extends BaseInputConnection { + private static final boolean DEBUG = false; + private static final String TAG = "EditableInputConnection"; + + private final TextView mTextView; + + // Keeps track of nested begin/end batch edit to ensure this connection always has a + // balanced impact on its associated TextView. + // A negative value means that this connection has been finished by the InputMethodManager. + private int mBatchEditNesting; + + private final InputMethodManager mIMM; + + private OnDelEventListener delEventListener; + + public EditableInputConnection(TextView textview) { + super(textview, true); + mTextView = textview; + mIMM = (InputMethodManager) textview.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + } + + @Override + public Editable getEditable() { + TextView tv = mTextView; + if (tv != null) { + return tv.getEditableText(); + } + return null; + } + + @Override + public boolean beginBatchEdit() { + synchronized (this) { + if (mBatchEditNesting >= 0) { + mTextView.beginBatchEdit(); + mBatchEditNesting++; + return true; + } + } + return false; + } + + @Override + public boolean endBatchEdit() { + synchronized (this) { + if (mBatchEditNesting > 0) { + // When the connection is reset by the InputMethodManager and reportFinish + // is called, some endBatchEdit calls may still be asynchronously received from the + // IME. Do not take these into account, thus ensuring that this IC's final + // contribution to mTextView's nested batch edit count is zero. + mTextView.endBatchEdit(); + mBatchEditNesting--; + return true; + } + } + return false; + } + + protected void reportFinish() { + synchronized (this) { + while (mBatchEditNesting > 0) { + endBatchEdit(); + } + // Will prevent any further calls to begin or endBatchEdit + mBatchEditNesting = -1; + } + } + + @Override + public boolean clearMetaKeyStates(int states) { + Editable content = getEditable(); + if (content == null) { + return false; + } + KeyListener kl = mTextView.getKeyListener(); + if (kl != null) { + try { + kl.clearMetaKeyState(mTextView, content, states); + } catch (AbstractMethodError e) { + // This is an old listener that doesn't implement the + // new method. + } + } + return true; + } + + @Override + public boolean commitCompletion(CompletionInfo text) { + if (DEBUG) { + Log.v(TAG, "commitCompletion " + text); + } + mTextView.beginBatchEdit(); + mTextView.onCommitCompletion(text); + mTextView.endBatchEdit(); + return true; + } + + /** + * Calls the {@link TextView#onCommitCorrection} method of the associated TextView. + */ + @Override + public boolean commitCorrection(CorrectionInfo correctionInfo) { + if (DEBUG) { + Log.v(TAG, "commitCorrection" + correctionInfo); + } + mTextView.beginBatchEdit(); + mTextView.onCommitCorrection(correctionInfo); + mTextView.endBatchEdit(); + return true; + } + + @Override + public boolean performEditorAction(int actionCode) { + if (DEBUG) { + Log.v(TAG, "performEditorAction " + actionCode); + } + mTextView.onEditorAction(actionCode); + return true; + } + + @Override + public boolean performContextMenuAction(int id) { + if (DEBUG) { + Log.v(TAG, "performContextMenuAction " + id); + } + mTextView.beginBatchEdit(); + mTextView.onTextContextMenuItem(id); + mTextView.endBatchEdit(); + return true; + } + + @Override + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { + if (mTextView != null) { + ExtractedText et = new ExtractedText(); + if (mTextView.extractText(request, et)) { + if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0) { + Reflector.invokeMethodExceptionSafe(mTextView, "setExtracting", + new Reflector.TypedObject(request, ExtractedTextRequest.class)); + } + return et; + } + } + return null; + } + + @Override + public boolean performPrivateCommand(String action, Bundle data) { + mTextView.onPrivateIMECommand(action, data); + return true; + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + if (mTextView == null) { + return super.commitText(text, newCursorPosition); + } + if (text instanceof Spanned) { + Spanned spanned = (Spanned) text; + SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class); + Reflector.invokeMethodExceptionSafe(mIMM, "registerSuggestionSpansForNotification", + new Reflector.TypedObject(spans, SuggestionSpan[].class)); + } + + Reflector.invokeMethodExceptionSafe(mTextView, "resetErrorChangedFlag"); + boolean success = super.commitText(text, newCursorPosition); + Reflector.invokeMethodExceptionSafe(mTextView, "hideErrorIfUnchanged"); + + return success; + } + + @Override + public boolean requestCursorUpdates(int cursorUpdateMode) { + if (DEBUG) { + Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode); + } + + // It is possible that any other bit is used as a valid flag in a future release. + // We should reject the entire request in such a case. + int KNOWN_FLAGS_MASK = InputConnection.CURSOR_UPDATE_IMMEDIATE | + InputConnection.CURSOR_UPDATE_MONITOR; + int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK; + if (unknownFlags != 0) { + if (DEBUG) { + Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." + + " cursorUpdateMode=" + cursorUpdateMode + + " unknownFlags=" + unknownFlags); + } + return false; + } + + if (mIMM == null) { + // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled. + // TODO: Return some notification code rather than false to indicate method that + // CursorAnchorInfo is temporarily unavailable. + return false; + } + Reflector.invokeMethodExceptionSafe(mIMM, "setUpdateCursorAnchorInfoMode", + new Reflector.TypedObject(cursorUpdateMode, int.class)); + if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) { + if (mTextView == null) { + // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored. + // TODO: Return some notification code for the input method that indicates + // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored. + } else { + // This will schedule a layout pass of the view tree, and the layout event + // eventually triggers IMM#updateCursorAnchorInfo. + mTextView.requestLayout(); + } + } + return true; + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + return delEventListener != null && delEventListener.onDelEvent() || super + .deleteSurroundingText(beforeLength, afterLength); + } + + public void setDelEventListener( + OnDelEventListener delEventListener) { + this.delEventListener = delEventListener; + } + + public interface OnDelEventListener { + boolean onDelEvent(); + } +} \ No newline at end of file diff --git a/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/Reflector.java b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/Reflector.java new file mode 100644 index 0000000..3346697 --- /dev/null +++ b/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/deledittext/Reflector.java @@ -0,0 +1,59 @@ +package com.moziqi.pwd.widget.deledittext; + +import java.lang.reflect.Method; + +public class Reflector { + public static class TypedObject { + private Object obj; + private Class clazz; + + public TypedObject(Object obj, Class clazz) { + this.obj = obj; + this.clazz = clazz; + } + } + + public static Object invokeMethodExceptionSafe(Object target, String methodName, + TypedObject... typedObjects) { + Object[] params = null; + Class[] paramClazzes = null; + if (typedObjects != null && typedObjects.length > 0) { + params = new Object[typedObjects.length]; + paramClazzes = new Class[typedObjects.length]; + + for (int i = 0; i < typedObjects.length; i++) { + params[i] = typedObjects[i].obj; + paramClazzes[i] = typedObjects[i].clazz; + } + } + + Method method; + Class targetClass = target.getClass(); + do { + method = getMethod(targetClass, methodName, paramClazzes); + if (method != null) { + break; + } + targetClass = targetClass.getSuperclass(); + } while (targetClass != Object.class); + + if (method != null) { + if(!method.isAccessible()) { + method.setAccessible(true); + } + try { + return method.invoke(target, params); + } catch (Exception e) { + } + } + return null; + } + + private static Method getMethod(Class target, String methodName, Class... types) { + try { + return target.getDeclaredMethod(methodName, types); + } catch (NoSuchMethodException e) { + } + return null; + } +} \ No newline at end of file