在日常開發(fā)過程中何之,Dialog的使用頻率應(yīng)該是僅次于Activity嚷辅,不管是出于UI交互原因還是業(yè)務(wù)需求煤墙,頁面內(nèi)經(jīng)常會需要各種彈窗。這篇文章中并不是講Dialog的使用也不是如何自定義各式各樣彈窗馍刮。而是從源碼的角度分析Dialog是如何展示在我們的屏幕中信夫,在Android系統(tǒng)中Dialog如何創(chuàng)建視圖、如何填充數(shù)據(jù)卡啰,如何添加Window中去静稻。通過這篇文章我算是記錄自己對Android視圖結(jié)構(gòu)的筆記。
那就直接開始碎乃,從具體的使用實例開始入手:
public void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);//步驟一
//步驟二
builder.setTitle("Title").
setMessage("message").
setIcon(R.drawable.ic_launcher_background).
setPositiveButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).
setNeutralButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).
setNegativeButton("Button3", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).
create(). //步驟三
show(); //步驟四
}
通過對上訴的代碼的觀察姊扔,AlertDialog明顯是個Builder構(gòu)建模式,AlertDialog.Builder同時扮演builder梅誓、ConcreteBuild恰梢、Director的角色佛南,把AlertDialog對象的構(gòu)建過程和創(chuàng)建過程分開。具體是怎么做的嵌言,我把上述分成了四給步驟:
步驟一 :通過AlertDialog.Builder構(gòu)建了AlertController.AlertParams(以下簡稱為P)對象,從命名上可以看出AlertParams——彈窗的各種參數(shù)嗅回。
public Builder(Context context) {
this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
步驟二:Builder的一些列setXXX方法,給第一步中構(gòu)建的P設(shè)置參數(shù)摧茴。
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
步驟三:Builder的create方法绵载,到這里才真正創(chuàng)建AlertDialog對象,并且在其構(gòu)造函數(shù)中創(chuàng)建了AlertController實例,然后再調(diào)用P的apply方法苛白,把變量P中保存的各類參數(shù)設(shè)置到AlertDialog的AlertController對象中,在Dialog的構(gòu)造方法中還獲取了一個重要的對象WindowManager娃豹,在后面我們需要拿WindowManager添加視圖到Window 上。
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
//代碼...
return dialog;
}
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//在父類Dialog構(gòu)造函數(shù)獲得一個重要的對象WindowManager
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = AlertController.create(getContext(), this, getWindow());
}
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
//代碼...
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
//代碼...
}
第四步:調(diào)用AlertDialog的show方法顯示彈窗购裙,也是我們分析的重點懂版,主要做了如下幾個事情;
- 通過dispatchOnCreate調(diào)用AlertDialog的onCreate方法:
- 然后在調(diào)用AlertDialog的onStart
- 最后將Dialog的DecorView添加到WindowManager
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) {
//調(diào)用生命周期onCreate
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);
}
//調(diào)用onStart
onStart();
mDecor = mWindow.getDecorView();
//代碼...
WindowManager.LayoutParams l = mWindow.getAttributes();
//代碼...
//windowManager.addView 添加視圖
mWindowManager.addView(mDecor, l);
if (restoreSoftInputMode) {
l.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
}
mShowing = true;
sendShowMessage();
}
在show方法中執(zhí)行了Dialog的一系列生命周期方法躏率,按照以往Activity的經(jīng)驗躯畴,而AlertDialog的內(nèi)容視圖構(gòu)建也應(yīng)該在onCreate方法中,進入AlertDialog 的onCreate方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
public void installContent() {
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
}
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
return mAlertDialogLayout;
}
其中主要調(diào)用AlertController的installContent方法薇芝,在installContent中調(diào)用我們熟悉的setContentView方法蓬抄,這里和Activity的設(shè)置視圖一模一樣,最終都是調(diào)用window的setContentView。而這里的contentView夯到,也是就是mAlertDialogLayout彈窗視圖布局嚷缭,其值是在AlerController構(gòu)造函數(shù)中進行的初始化。
protected AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
//代碼...
//alert_dialog.xml
final TypedArray a = context.obtainStyledAttributes(null,
R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
//代碼...
}
在setContentView中主要做的事情是創(chuàng)建一個DecorView耍贾,根據(jù)Theme峭状,F(xiàn)eature添加了對應(yīng)的布局文件,再把我們contentView添加到DecorView中逼争。接下來再看setupView方法:
private void setupView() {
// 獲取初始化內(nèi)容視圖
final View parentPanel = mWindow.findViewById(R.id.parentPanel);
//獲取title區(qū)域
final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
// Install custom content before setting up the title or buttons so
// that we can handle panel overrides.
final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
setupCustomContent(customPanel);
final View customTopPanel = customPanel.findViewById(R.id.topPanel);
final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
// Resolve the correct panels and remove the defaults, if needed.
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
/*初始化各視圖*/
setupContent(contentPanel);
setupButtons(buttonPanel);
setupTitle(topPanel);
//代碼...
a.recycle();
}
在setupView中就是初始化Alert Dialog布局中的各個部分,如標題欄劝赔、按鈕欄誓焦、內(nèi)容區(qū)域等等,在這個方法調(diào)用完后整個Dialog視圖內(nèi)容部分也就完成了着帽。最后再把在通過WindowManager把DecorView添加到添加到Window上杂伟,并顯示出來,正Dialog就出現(xiàn)在屏幕當中仍翰。
到這里已經(jīng)把Dialog的主要流程分析完了赫粥。