博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AlertDialog实现原理
阅读量:4179 次
发布时间:2019-05-26

本文共 12115 字,大约阅读时间需要 40 分钟。

对于一个页面的窗口展示,很多时候都可以理解为是一个activity,这样理解没错,但更确切的说window才是负责页面展示的,那为什么可以理解为一个页面就是一个activity呢?这是由于activity持有window对象,activity负责加载布局并将布局添加到window对象中。这里需要想个问题,dialog的显示并不由activity来控制,所以这里可以推测dialog的显示应该是由window来控制的,那到底是不是呢?那就得去源码中看看了。

先来个简单的使用:

mTextMessage.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);                builder.setTitle("titile");                builder.setMessage("message");                AlertDialog dialog = builder.create();                dialog.show();            }        });

使用起来还是挺简单的,就从这段简单的代码开始入手了,先来看下AlertDialog.Builder的构造函数:

public Builder(@NonNull Context context) {            this(context, resolveDialogTheme(context, 0));        }        public Builder(@NonNull Context context, @StyleRes int themeResId) {            P = new AlertController.AlertParams(new ContextThemeWrapper(                    context, resolveDialogTheme(context, themeResId)));            mTheme = themeResId;        }

初始化了一个AlertController.AlertParams类型的P对象,可以理解为是一个存放设置dialog属性的容器,再来看下AlertController.AlertParams的构造函数:

public AlertParams(Context context) {            mContext = context;            mCancelable = true;            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        }

其实就是初始化了一些成员变量。

到这AlertDialog.Builder的初始化就算完了,还是比较简单的,接下来看dialog的参数是怎么设置的:

builder.setTitle("titile")        builder.setMessage("message")

这是设置dialog的显示信息,当然还有好多属性可以设置,不过都是类似的,所以这里就看这两个属性的实现:

public Builder setTitle(@Nullable CharSequence title) {            P.mTitle = title;            return this;        }        public Builder setMessage(@Nullable CharSequence message) {            P.mMessage = message;            return this;        }

可以看到,这些设置进来的属性都赋值给了P对象,到目前为止,已经设置了显示dialog的消息,接下来执行的是:

AlertDialog dialog = builder.create();

看来得去看看create()方法中做了些什么了:

public AlertDialog create() {            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,            // so we always have to re-set the theme            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);            P.apply(dialog.mAlert);            dialog.setCancelable(P.mCancelable);            if (P.mCancelable) {                dialog.setCanceledOnTouchOutside(true);            }            dialog.setOnCancelListener(P.mOnCancelListener);            dialog.setOnDismissListener(P.mOnDismissListener);            if (P.mOnKeyListener != null) {                dialog.setOnKeyListener(P.mOnKeyListener);            }            return dialog;        }

先创建了AlertDialog对象,那就去看下AlertDialog的构造方法了:

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {        super(context, resolveDialogTheme(context, themeResId));        mAlert = new AlertController(getContext(), this, getWindow());    }

这里创建了一个AlertController类型的mAlert对象,create()方法中有用到,AlertDialog继承自Dialog:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {        if (createContextThemeWrapper) {            if (themeResId == ResourceId.ID_NULL) {                final TypedValue outValue = new TypedValue();                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);                themeResId = outValue.resourceId;            }            mContext = new ContextThemeWrapper(context, themeResId);        } else {            mContext = context;        }        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        final Window w = new PhoneWindow(mContext);        mWindow = w;        w.setCallback(this);        w.setOnWindowDismissedCallback(this);        w.setOnWindowSwipeDismissedCallback(() -> {            if (mCancelable) {                cancel();            }        });        w.setWindowManager(mWindowManager, null, null);        // 设置window显示的位置,默认是居中显示        w.setGravity(Gravity.CENTER);        mListenersHandler = new ListenersHandler(this);    }

这里主要是创建了一个Window对象(Window的唯一实现是PhoneWindow),然后设置了一些回调和Window显示的位置,所以Dialog是持有Window对象的。在回到create()方法中:

public AlertDialog create() {            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,            // so we always have to re-set the theme            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);            P.apply(dialog.mAlert);            dialog.setCancelable(P.mCancelable);            if (P.mCancelable) {                dialog.setCanceledOnTouchOutside(true);            }            dialog.setOnCancelListener(P.mOnCancelListener);            dialog.setOnDismissListener(P.mOnDismissListener);            if (P.mOnKeyListener != null) {                dialog.setOnKeyListener(P.mOnKeyListener);            }            return dialog;        }

创建AlertDialog对象后,接着调用的是P.apply(dialog.mAlert),P对象是在构建Builder的时候创建的,其类型是AlertController.AlertParams,mAlert对象在AlertDialog构造函数初始化的,那就来看下apply()函数做了些什么:

public void apply(AlertController dialog) {            if (mCustomTitleView != null) {                dialog.setCustomTitle(mCustomTitleView);            } else {                if (mTitle != null) {                    dialog.setTitle(mTitle);                }                if (mIcon != null) {                    dialog.setIcon(mIcon);                }                if (mIconId != 0) {                    dialog.setIcon(mIconId);                }                if (mIconAttrId != 0) {                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));                }            }            if (mMessage != null) {                dialog.setMessage(mMessage);            }            if (mPositiveButtonText != null || mPositiveButtonIcon != null) {                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,                        mPositiveButtonListener, null, mPositiveButtonIcon);            }            if (mNegativeButtonText != null || mNegativeButtonIcon != null) {                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,                        mNegativeButtonListener, null, mNegativeButtonIcon);            }            if (mNeutralButtonText != null || mNeutralButtonIcon != null) {                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,                        mNeutralButtonListener, null, mNeutralButtonIcon);            }            // For a list, the client can either supply an array of items or an            // adapter or a cursor            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {                createListView(dialog);            }            if (mView != null) {                if (mViewSpacingSpecified) {                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,                            mViewSpacingBottom);                } else {                    dialog.setView(mView);                }            } else if (mViewLayoutResId != 0) {                dialog.setView(mViewLayoutResId);            }            /*            dialog.setCancelable(mCancelable);            dialog.setOnCancelListener(mOnCancelListener);            if (mOnKeyListener != null) {                dialog.setOnKeyListener(mOnKeyListener);            }            */        }

前面有说到使用Builder设置参数时,实际是将参数设置到P对象中,所以apply()函数的作用就是将P对象中参数又设置到了mAlert对象中。接着回到create()函数中看剩下的方法,基本是对dialog的回调设置,这里就不跟进去了。

分析到这,dialog的配置就算是全部配置完了,接下去要执行的是:

dialog.show();

嗯嗯,看来实现就在这个方法中了:

public void show() {        if (mShowing) {            if (mDecor != null) {                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);                }                mDecor.setVisibility(View.VISIBLE);            }            return;        }        mCanceled = false;        if (!mCreated) {            dispatchOnCreate(null);        } else {            // Fill the DecorView in on any configuration changes that            // may have occured while it was removed from the WindowManager.            final Configuration config = mContext.getResources().getConfiguration();            mWindow.getDecorView().dispatchConfigurationChanged(config);        }        onStart();        mDecor = mWindow.getDecorView();        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {            final ApplicationInfo info = mContext.getApplicationInfo();            mWindow.setDefaultIcon(info.icon);            mWindow.setDefaultLogo(info.logo);            mActionBar = new WindowDecorActionBar(this);        }        WindowManager.LayoutParams l = mWindow.getAttributes();        if ((l.softInputMode                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();            nl.copyFrom(l);            nl.softInputMode |=                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;            l = nl;        }        mWindowManager.addView(mDecor, l);        mShowing = true;        sendShowMessage();    }

先是if(mShowing)判断dialog是否处在显示状态,初次显示这里为false,那这里这样做判断有什么用意呢?在Dialog中有个hide()方法,这个方法是隐藏dialog,实际是没用销毁dialog的,且dialog的mShowing还是为true的,这就是这里所作判断的用意,继续往下看,接下来是if(!mCreated),默认是false,所以这里要执行的是dispatchOnCreate(null):

void dispatchOnCreate(Bundle savedInstanceState) {        if (!mCreated) {            onCreate(savedInstanceState);            mCreated = true;        }    }    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mAlert.installContent();    }

在执行完dispatchOncreate(null)后将mCreated置为true,紧接着执行是onCreate(),这个在Dialog中是空实现,在AlertDialog有实现,就执行了mAlert.installContent(),作用是什么呢?看看就知道了:

public void installContent() {        final int contentView = selectContentView();        mDialog.setContentView(contentView);        setupView();    }

这里简单说下这几行代码的作用:

1、selectContentView()是获取显示dialog的布局文件;

2、mDialog.setContentView(),这里是将布局文件设置到window对象中去,还记得activity是如何设置view的么?没错,用的就是setContentView()这个方法,这里和activity中使用是一个意思,其实质都是将布局文件设置到window对象中,之后就可显示了。

3、将布局文件设置到window对象中,会生成view对象,但是这些view对象还没有设置内容和一些事件监听,这也就是setupView()要做的事;

做完这些后就可以回到show()方法中接着往下走了,mWindowManager.adddView()这个方法,这个方法实际起到什么用呢?那就的好好说下了,在这方法中会创建一个ViewRootImpl对象,然后将view对象添加到ViewRootImpl对象中去,ViewRootImpl中会通知下一帧屏幕信号到来时执行view的测量、布局、绘制,这样dialog就显示到界面上了。

最后在说下sendShowMessage()方法,当设置了显示dialog时的回调监听时,这里实际的作用就是去通知这个回调。

接下来看下Dialog的一些不常见设置,也可以说是一些高级点的设置,上面说到dialog实际也是有window来显示,那么就可以对window进行一些设置,已达到想要的效果,这里先说几点需求:

1、指定dialog的宽高;

2、控制dialog的透明度;

3、调整dialog外部的灰度;

4、指定dialog的显示位置;

5、设置dialog的出现和消失时的动画;

如果你对window对象不太明白,那这个实现起来那就会有点头大了,但是当你明白window对象后,你就会发现这实在是太简单了,因为只需要设置一些window的属性就可以了,来看看:

mTextMessage.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);                builder.setTitle("titile");                builder.setMessage("message");                AlertDialog dialog = builder.create();                dialog.show();                Window window = dialog.getWindow();                WindowManager.LayoutParams params = window.getAttributes();                params.width = 200;                params.height = 100;                params.alpha = 1f;                params.dimAmount = 0.5f;                params.gravity  = Gravity.BOTTOM;                params.windowAnimations = R.style.BaseDialog_Bottom_Anim;                window.setAttributes(params);                dialog.show();            }        });

这里稍微提下这几个属性,还有其他的一些属性,自己可以去测试下,代码还是比较简单的:

params.width = 200;params.height = 100;  设置dialog显示的宽高;

params.alpha = 1f;params.dimAmount = 0.5;  设置弹框的透明度和弹框外部的灰度;

params.gravity = Gravity.BOTTOM;  设置dialog显示的位置在底部,如果想指定在具体位置,可以使用params.x;

params.windowAnimations = R.style.BaseDialog_Botton_Anim;  设置dialog显示隐藏动画;

R.style.BaseDialog_Botton_Anim的配置如下:

netlib_bottom_enter_anim:

netlib_bottom_exit_anim:

到这就算说完了,有不明白的欢迎提问一起探讨!

 

 

 

 

转载地址:http://skhai.baihongyu.com/

你可能感兴趣的文章
60个优秀的免费3D模型下载网站
查看>>
Cardboard虚拟现实开发初步(三)
查看>>
Android native和h5混合开发几种常见的hybrid通信方式
查看>>
Vista/Win7 UAC兼容程序开发指南
查看>>
IOS程序开发框架
查看>>
安装jdk的步骤
查看>>
简述JAVA运算符
查看>>
简易ATM源代码及运行结果
查看>>
简述Java中的简单循环
查看>>
用JAVA实现各种乘法表
查看>>
for双重循环实现图形
查看>>
Java类和对象基础
查看>>
简述Java继承和多态
查看>>
Java中Arrays工具类的用法
查看>>
简述JAVA抽象类和接口
查看>>
JAVA常用基础类
查看>>
简述Java异常处理
查看>>
简述Java集合框架
查看>>
jQuery+ajax实现省市区(县)下拉框三级联动
查看>>
Spring中的AOP 面向切面编程
查看>>