在Android应用的某个界面上,配置出现使用自定义键盘,系统键盘的相邻的两个独立的输入框,当用户在两个输入框之间来回切换的时候,会出现弹跳闪烁问题。
这个问题发生的原因是,系统键盘的关闭是异步的,我们需要等待系统键盘关闭后再显示我们的键盘,否则会出现系统键盘关闭的过程中,我们自定义键盘同时出现,出现两者叠加的瞬间状态。
解决方法参考下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.ResultReceiver; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import java.lang.reflect.Method; public class PasswordEdit extends EditText { // 主线程通知调用结果 private final static Handler handler = new Handler(Looper.getMainLooper()); public PasswordEdit(@NonNull final Context context) { super(context); initPasswordEdit(context); } public PasswordEdit(@NonNull final Context context, @Nullable final AttributeSet attr) { super(context, attr); initPasswordEdit(context); } public PasswordEdit(@NonNull final Context context, @Nullable final AttributeSet attr, int defStyleAttr) { super(context, attr, defStyleAttr); initPasswordEdit(context); } public PasswordEdit(@NonNull final Context context, @Nullable final AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initPasswordEdit(context); } private void doStartPasswordEditKeyBoard(@NonNull final Context context) { final InputMethodManager imm = ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)); //关闭键盘,如果键盘正在显示,或者正在关闭,等待键盘关闭完成的通知 final boolean wait = imm.hideSoftInputFromWindow(this.getWindowToken(), 0, new ResultReceiver(handler) { @Override protected void onReceiveResult(final int resultCode, @Nullable final Bundle resultData) { // 收到通知时,需要检查一下焦点是否已经移走,如果已经不在了,就不需要弹出键盘了 if (PasswordEdit.this.hasFocus()) { // 此处需要递归处理,原因为异步状态下,收到通知的时候,键盘可能已经处于弹出状态了 doStartPasswordEditKeyBoard(context); } } }); // 键盘已经处于隐藏状态 if (!wait) { StartPasswordKeyBoard(); } } // 阻止系统键盘在输入框获得焦点的时候自动弹出 private void disableShowSoftInputOnFocus() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { this.setShowSoftInputOnFocus(false); } else { try { Method method = EditText.class.getMethod("setShowSoftInputOnFocus", Boolean.TYPE); method.setAccessible(true); method.invoke(this, false); } catch (Exception e) { //e.printStackTrace(); } } } @SuppressLint("ClickableViewAccessibility") private void initPasswordEdit(@NonNull final Context context) { this.setLongClickable(false); this.disableShowSoftInputOnFocus(); this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(@NonNull final View view, @NonNull final MotionEvent motionEvent) { doStartPasswordEditKeyBoard(context); return true; } }); } public void StartPasswordKeyBoard() { //显示自定义的密码键盘的代码 } } |
注意,在继承实现自定义的密码键盘弹窗的时候,可以参考Android源代码中的PopWindow的处理逻辑。如果是使用WindowManager.addView
的方式添加到窗口,那么在关闭View的时候需要使用 WindowManager.removeViewImmediate
来移除,否则一样会出现与输入法弹窗冲突。 代码同样参考PopWindow。
另外,如果自定义输入法的View增加了 FLAG_NOT_TOUCH_MODAL 属性,那么会导致事件穿透到输入法窗口下面的Window,如果点击区域恰好有个 EditText,则可能诱发系统输入法的弹出。如果自定义键盘通过监听 ACTION_OUTSIDE来关闭窗口,那么会出现系统键盘先弹出,输入法的View再关闭的奇怪流程。因此这种情况下,我们需要参考PopWindow来监听ACTION_DOWN事件。
如下图:
监测系统键盘是否已经关闭,参考代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
ViewCompat.setWindowInsetsAnimationCallback(this, new WindowInsetsAnimationCompat.Callback(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) { @NonNull @Override public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) { return insets; } @Override public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { if (animation.getTypeMask() == WindowInsetsCompat.Type.ime()) { final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(View.this); if(null != insets) { // 此处判断键盘是否已经被关闭,已经关闭返回true,否则返回false insets.isVisible(WindowInsetsCompat.Type.ime()); } } } }); |
上面的代码依赖
1 |
androidx.core:core:1.6.0 |
参考链接
- 项目实战:WindowManager中removeView的那些坑-随心所欲removeView
- Android WindowManager及其动画问题
- WindowManager addViw时添加自定义动画效果
- How can I use ResultReceiver in InputMethodManager#hideSoftInputFromWindow
- Android InputMethodManager输入法简介
- Android hideSoftInputFromWindow方法参数中flag如何选用
- Disable keyboard on EditText
- android安卓屏蔽禁用系统输入法,自定义软键盘,解决EditText光标问题demo
- android开发(45) 自定义软键盘(输入法)
- How to capture the “virtual keyboard show/hide” event in Android?
- Android 11: Creating an IME(Keyboard) Visibility Listener
- 适配Android R键盘动画WindowInsetsAnimation新api的demo,语言为Java
- Detect the Keyboard
- 安卓应用如何在整个应用运行期间都禁止弹出输入法面板?
- getWindow().addFlags( WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)的用法