想要源码的请戳这里:源码地址
实现效果
实现原理
概括:数字键盘View
用Popuwindow
来呈现,通过监听Activity/Dialog中Window
的decorView
焦点改变事件,以及EditText
中的tag
属性,来动态显示自定义的数字键盘。在Dialog中弹出自定义数字键盘,需要动态更新数字键盘的位置,和宽度。
初始化数字键盘
设置数字键盘的宽高,并监听焦点变化:
private void initKeyboard(Window window, boolean isAttachActivity) {
ViewGroup keyboardRootView = (ViewGroup) LayoutInflater.from(window.getContext()).inflate(R.layout.keyboard_container, null);
DisplayMetrics dm = new DisplayMetrics();
window.getWindowManager().getDefaultDisplay().getMetrics(dm);
int windowWidth = dm.widthPixels;
int height = KeyboardUtils.getKeyboardHeight(window);
mKeyboard = new BkjfKeyboard(window, keyboardRootView, windowWidth, height, isAttachActivity);
mKeyboard.setOnKeyboardActionListener(new DefaultActionListener());
window.getDecorView().getViewTreeObserver().addOnGlobalFocusChangeListener(new DefaultOnGlobalFocusChangeListener());
hideKeyboard();
}
监听焦点变化
private class DefaultOnGlobalFocusChangeListener implements ViewTreeObserver.OnGlobalFocusChangeListener {
/**
* 是否是第一次进入
*/
private boolean isFirstEnter = true;
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
if (mEditText != newFocus) {
mEditText = null;
}
if (newFocus instanceof EditText) {
mEditText = (EditText) newFocus;
String tag = (String) mEditText.getTag();
if (tag != null) {
showSoftInputs(mEditText, SET_SHOW_SOFT_INPUTS_ON_FOCUS);
setHideKeyboardListener();
setShowKeyboardListener();
if(isFirstEnter && mDismissWhenStart){
mKeyboard.dismiss();
isFirstEnter = false;
return ;
}
hideDefaultKeyboard();
showKeyboard(mEditText);
return;
}
}
/**
* 如果没有tag,就隐藏自定义键盘
*/
hideKeyboard();
}
......
}
当view
树的焦点变化时,并且焦点聚焦到EditText
上,就去获取此时EditText
的tag
属性,从而显示出数字键盘。
根据tag获取数字键盘
/**
* 根据键盘的inputType获取相应的键盘View
* 默认提供数字键盘
*/
private IKeyboardView getKeyboardView(String rawTypes) {
if (rawTypes == null) {
return null;
}
String type = KeyboardUtils.getInputType(rawTypes);
String flag = KeyboardUtils.getInputFlag(rawTypes);
IKeyboardView keyboardView = keyboardViews.get(type);
if (keyboardView == null) {
switch (type) {
case NumberInputType.INPUT_TYPE_NUMBER:
keyboardView = new NumberKeyboardView(mWindow);
keyboardViews.put(type, keyboardView);
break;
default: {
try {
keyboardView = KeyboardUtils.getKeyboardViewFromInputType(type, mWindow);
keyboardViews.put(type, keyboardView);
} catch (Exception e) {
throw new RuntimeException("cannot recognize tag,please refer the doc", e);
}
}
}
}
keyboardView.setOnActionListener(mOnActionListener);
keyboardView.setKeyFlag(flag);
return keyboardView;
}
Dialog中弹自定义数字键盘细节
首先,需要设置dialog
的gravity
属性,强制将dialog设置在窗口的顶部:
dialog.getWindow().setGravity(Gravity.TOP);
其次,在显示数字键盘时,需要动态更新数字键盘的位置和宽高:
IBinder windowToken = mEditText.getWindowToken();
if (windowToken != null && windowToken.isBinderAlive()) {
Rect rect = new Rect();
mEditText.getRootView().getGlobalVisibleRect(rect);
Context editTextContext = mEditText.getContext();
Resources resources = editTextContext.getApplicationContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
int y = rect.bottom - dm.heightPixels;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
setAttachedInDecor(false);
}
setTouchable(true);
showAtLocation(mEditText, Gravity.BOTTOM | Gravity.LEFT, 0, y);
update(getWidth(), getHeight());
}
mEditText
对象为Dialog实例中的EditText对象。
Dialog.dismiss()的坑
我们在监听view
树的焦点变化时,是通过当前view
所在window
来实现的:
window.getDecorView().getViewTreeObserver().addOnGlobalFocusChangeListener(new DefaultOnGlobalFocusChangeListener());
当我们默认实现dialog时,点击外部半透明部分,它会去调用dialog.dismiss(),这时候它是会把dialog上的mDecor
实例删除掉:
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
...
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
所以,我们在每次调用dialog.show()之前,需要重新去设置一次popuwindow,重新去监听view树的焦点变化。
当然了,也可以将dialog
的外部蒙层部分,设置点击无响应,或者给外部蒙层部分设置点击事件,调用dialog.hide()
,毕竟dialog.hide()
只是将dialog中mDecor
对象的可见性设置为GONE
。如果这样做的话,需要将dialog
和数字键盘绑定起来,在调用dialog.hide()
时,来实现隐藏数字键盘。目前写的数字键盘组件,跟dialog
是完全分离的。