Dialog和软键盘在屏幕上的并存问题:

最近在优化项目登陆模块的用户体验,验证码登陆的时候出现了这个问题,当弹出加载框的时候自动把之前打开的软键盘给隐藏掉了,感觉用户体验不太好。在网上也搜不到针对这个问题的解答,最后还是想看看源码中有没有什么蛛丝马迹,果然还是通过源码解决了这个问题,不得感叹一下:关键时刻还是源码好使啊!

首先说下结论:
1.AlertDialog和ProgressDialog默认可以和系统软键盘并存与同意屏幕(其实质是并存于同一个window窗口,具体下面会解释)
2.Dialog以及用户自定义的继承自Dialog的弹出框默认不可以和软键盘并存与同一屏幕
如果Dialog对象或者自定义弹出框想要和软键盘共存与同一屏幕,可以进行如下设置:
2.1Dialog对象设置

Dialog di = new Dialog(MainActivity.this);
    di.setTitle("test   test");
    di.getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

2.2自定义继承自Dialog对象的设置

public class LoadingDialog extends Dialog {
                            ...
        }
    loadDialog = new LoadingDialog(this, R.style.dialog);
    loadDialog.getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

结论分析:
解析思路预览:
1. Activity是什么?Dialog是什么?软键盘的实质是什么?他们三个和Window窗口的关系是什么?
2. Dialog和软键盘的关系是什么?
3. 是谁控制Dialog和软键盘的显示的?怎么控制?
4. 得出结论:如何让Dialog和软键盘并存?


  1. 1.1先从我们最熟悉的Activity说起:
    源码中Acitvity的部分注释如下:
/**
 * An activity is a single, focused thing that the user can do.  Almost all
 * activities interact with the user, so the Activity class takes care of
 * creating a window for you in which you can place your UI with
 * {@link #setContentView}.  While activities are often presented to the user
 * as full-screen windows, they can also be used in other ways: as floating
 * windows (via a theme with {@link android.R.attr#windowIsFloating} set)
 * or embedded inside of another activity (using {@link ActivityGroup}).
 *......
 */
 public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2 {...}

从中得知:Acitvity的布局是添加在所创建的window窗口中的。

1.2这段注释说的很明白,如果我们想要把dialog放在界面中软键盘的上面显示,就需要给当前的window设置FLAG_ALT_FOCUSABLE_IM这个属性。

/** 

Often you will want to have a Dialog display on top of the current * input method, because there is no reason for it to accept text. You can * do this by setting the {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} window flag (assuming * your Dialog takes input focus, as it the default) with the following code: */ public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener { ... /** * Create a Dialog window that uses the default dialog frame style. * * @param context The Context the Dialog is to run it. In particular, it * uses the window manager and theme in this context to * present its UI. */ public Dialog(Context context) { this(context, 0, true); } /** * Create a Dialog window that uses a custom dialog style. * * @param context The Context in which the Dialog should run. In particular, it * uses the window manager and theme from this context to * present its UI. * @param theme A style resource describing the theme to use for the * window. See Style * and Theme Resources for more information about defining and using * styles. This theme is applied on top of the current theme in * context. If 0, the default dialog theme will be used. */ public Dialog(Context context, int theme) { this(context, theme, true); } Dialog(Context context, int theme, boolean createContextWrapper) { if (theme == 0) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } ... /** * Set the screen content to an explicit view. This view is placed * directly into the screen's view hierarchy. It can itself be a complex * view hierarhcy. * * @param view The desired content to display. */ public void setContentView(View view) { mWindow.setContentView(view); } ... }

通过源码中构造方法的注释“Create a Dialog window”,可以看出来,dialog需要创建一个属于自己的window窗口;
并且在它的构造方法中就初始化创建了这个window的对象:
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
之后把要加载的dialog的布局设置到window窗口上:
public void setContentView(View view) {
mWindow.setContentView(view);
}

从中得知:显示dialog是需要创建一个新的window窗口的。

1.3软键盘的实质就是一个自定义的Dialog,这里不再细说

1.4所以,activity和dialog都是依附于window窗口存在于手机界面上的,软键盘也是一个dialog。

2.要显示的dialog和软键盘是什么关系
由于软键盘也是一个自定义dialog,那么实质上就是我们的自定义dialog和软键盘的并存关系了。

3.由于dialog是依附于window窗口存在的,所以是由window的属性控制dialog和软键盘的显示。
dialog中源码注释已经说明如果需要dialog显示在软键盘之上,就需要为window添加FLAG_ALT_FOCUSABLE_IM这个属性。我们通过继承Dialog自定义的弹出窗口也需要为他们所在的window设置这个属性才可以和软键盘并存。
Dialog和软键盘并存分为两种情况:
一是,dialog布局中没有编辑框,dialog只是浮现在软键盘上面,实质是软件在下面acitivity所在的window中而dialog存在与自身所创建的window中,例如google给我们做的AlertDialog和ProgressDialog都是这样,这两个弹框默认都可以和软键盘并存并且浮现在软键盘之上的,因为这两个弹框默认添加到设置了该属性的window窗口的。
AlertDialog源码注释如下:

/** 

The AlertDialog class takes care of automatically setting * {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether * any views in the dialog return true from {@link View#onCheckIsTextEditor() * View.onCheckIsTextEditor()}. Generally you want this set for a Dialog * without text editors, so that it will be placed on top of the current * input method UI. You can modify this behavior by forcing the flag to your * desired mode after calling {@link #onCreate}. */ public class AlertDialog extends Dialog implements DialogInterface {...}

另外一种情况,dialog布局中有编辑框,需要使用软键盘进行文本编辑,我们自定义的继承自Dialog的弹框默认就可以实现在当前window中弹出软键盘。

4.结论见开篇,不再赘述

你可能感兴趣的:(Android开发)