編輯:關於Android編程
前面兩篇文章,我們分析了Activity的布局文件加載、繪制流程,算是對整個Android系統中界面的顯示流程有了一個大概的了解,其實Android系統中所有的顯示控件(注意這裡是控件,而不是組件)的加載繪制流程都是類似的,包括:Dialog的加載繪制流程,PopupWindow的加載繪制流程,Toast的顯示原理等,上一篇文章中,我說在介紹了Activity界面的加載繪制流程之後,就會分析一下剩余幾個控件的顯示控制流程,這裡我打算先分析一下Dialog的加載繪制流程。
可能有的同學問這裡為什麼沒有Fragment?其實嚴格意義上來說Fragment並不是一個顯示控件,而只是一個顯示組件。為什麼這麼說呢?其實像我們的Activity,Dialog,PopupWindow以及Toast類的內部都管理維護著一個Window對象,這個Window對象不但是一個View組件的集合管理對象,它也實現了組件的加載與繪制流程,而我們的Fragment組件如果看過源碼的話,嚴格意義上來說,只是一個View組件的集合並通過控制變量實現了其特定的生命周期,但是其由於並沒有維護Window類型的成員變量,所以其不具備組件的加載與繪制功能,因此其不能單獨的被繪制出來,這也是我把它稱之為組件而不是控件的原因。(在分析完這幾個控件的加載繪制流程之後,有時間的話,也會分析一下Fragment的相關源碼)
好吧,開始我們今天關於Dialog的講解,相信大家在平時的開發過程中經常會使用到Dialog彈窗,使用Dialog可以在Activity彈出彈窗,確認消息等。為了更好的分析Dialog的源碼,我們這裡暫時寫一個簡單的demo,看一下Dialog的使用實例。
title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setMessage("this is the content view!!!");
builder.setTitle("this is the title view!!!");
builder.setView(R.layout.activity_second);
builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
alertDialog = builder.create();
alertDialog.show();
}
});
我們在Activity中獲取一個textView組件,並監聽TextView的點擊事件,並在點擊事件中,初始化一個AlertDialog彈窗,並執行AlertDialog的show方法展示彈窗,在彈窗中定義一個按鈕,並監聽彈窗按鈕的點擊事件,若用戶點擊了彈窗的按鈕,則執行AlertDialog的dismiss方法,取消展示AlertDialog。好吧,我們來看一下這個彈窗彈出的展示結果:
可以看到我們定義的icon,title,message和button都已經顯示出來了,這時候我們點擊彈窗按鈕知道了,這時候彈窗就會消失了。
一般我們使用Dialog的大概流程都是這樣的,可能定制Dialog的時候有一些定制化的操作,但是基本操作流程還是這樣的。
那麼我們先來看一下AlertDialog.Builder的構造方法,這裡的Builder是AlertDialog的內部類,用於封裝AlertDialog的構造過程,看一下Builder的構造方法:
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
好吧,這裡調用的是Builder的重載構造方法:
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
那麼這裡的P是AlertDialog.Builder中的一個AlertController.AlertParams類型的成員變量,可見在這裡執行了P的初始化操作。
public AlertParams(Context context) {
mContext = context;
mCancelable = true;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
可以看到這裡主要執行了AlertController.AlertParams的初始化操作,初始化了一些成員變量。這樣執行了一系列操作之後我們的代碼:
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
就已經執行完成了,然後我們調用了builder.setIcon方法,這裡看一下setIcon方法的具體實現:
public Builder setIcon(@DrawableRes int iconId) {
P.mIconId = iconId;
return this;
}
可以看到AlertDialog的Builder的setIcon方法,這裡執行的就是給類型為AlertController.AlertParams的P的mIconId賦值為傳遞的iconId,並且這個方法返回的類型就是Builder。
然後我們調用了builder.setMessage方法,可以看一下builder.setMessage方法的具體實現:
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
好吧,這裡跟setIcon方法的實現邏輯類似,都是給成員變量的mMessage賦值為我們傳遞的Message值,且和setIcon方法類似的,這個方法返回值也是Builder。
再看一下builder.setTitle方法:
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
可以發現builder的setIcon、setMessage、setTitle等方法都是給Builder的成員變量P的icon,message,title賦值。
然後我們看一下builder.setView方法:
public Builder setView(int layoutResId) {
P.mView = null;
P.mViewLayoutResId = layoutResId;
P.mViewSpacingSpecified = false;
return this;
}
可以發現這裡的setView和setIcon,setMessage,setTitle等方法都是類似的,都是將我們傳遞的數據值賦值給Builder的成員變量P。
然後我們調用了builder.setPositiveButton方法:
builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
好吧,這裡我們看一下builder的setPositiveButton的源碼:
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
好吧,可以發現跟上面幾個方法還是類似的,都是為Builder的成員變量P的相應成員變量賦值。。。
上面的幾行代碼我們都是調用的builder.setXXX等方法,主要就是為Builder的成員變量P的相應成員變量值賦值。並且setXX方法返回值都是Builder類型的,因此我們可以通過消息琏的方式連續執行:
builder.setIcon().setMessage().setTitle().setView().setPositiveButton()...
這樣代碼顯得比較簡潔,set方法的執行順序是沒有固定模式的,這裡多說一下,這種編程方式很優秀,平時我們在設計構造類工具類的時候也可以參考這種模式,構造類有不同的功能或者特性,並且都不是必須的,我們可以通過set方法設置不同的特性值並返回構造類本身。
然後我們調用了builder.create方法,並且這個方法返回了AlertDialog。
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);
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(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
可以看到這裡首先調用了super的構造方法,而我們的AlertDialog繼承於Dialog,所以這裡執行的就是Dialog的構造方法,好吧,繼續看一下Dialog的構造方法:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
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.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
可以發現在Dialog的構造方法中直接直接構造了一個PhoneWindow,並賦值給Dialog的成員變量mWindow,從這裡可以看出其實Dialog和Activity的顯示邏輯都是類似的,都是通過對應的Window變量來實現窗口的加載與顯示的。然後我們執行了一些Window對象的初始化操作,比如設置回調函數為本身,然後調用了Window類的setWindowManager方法,並傳入了WindowManager,可以發現這裡的WindowManager對象是通過方法:
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
獲取的,而我們的context傳入的是Activity對象,所以這裡的WindowManager對象其實和Activity獲取的WindowManager對象是一致的。然後我們看一下window類的setWindowManager方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
可以看到跟Activity的Window對象的windowManager的獲取方式是相同的,都是通過new的方式創建一個新的WindowManagerImpl對象。好吧,繼續回到我們的AlertDialog的構造方法中,在構造方法中,我們除了調用Dialog的構造方法之外還執行了:
mAlert = new AlertController(getContext(), this, getWindow());
相當於初始化了AlertDiaog的成員變量mAlert。
繼續回到我們的AlertDialog.Builder.create方法,在創建了一個AlertDialog之後,又執行了P.apply(dialog.mAlert);
我們知道這裡的P是一個AlertController.AlertParams的變量,而dialog.mAlert是我們剛剛創建的AlertDialog中的一個AlertController類型的變量,我們來看一下apply方法的具體實現:
ublic 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) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
}
// 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);
}
}
看到了麼?就是我們在初始化AlertDialog.Builder的時候設置的icon、title、message賦值給了AlertController.AlertParams,這裡就是將我們初始化時候設置的屬性值賦值給我們創建的Dialog對象的mAlert成員變量。。。。
繼續我們的AlertDialog.Builder.create方法,在執行了AlertController.AlertParams.apply方法之後又調用了:
dialog.setCancelable(P.mCancelable);
可以發現這個也是AertController.AlertParams的一個成員變量,我們在初始化AlertDialog.Builder的時候也可以通過設置builder.setCancelable賦值,由於該屬性為成員變量,所以默認值為false,而我們並沒有通過builder.setCancelable修改這個屬性值,所以這裡設置的dialog的cancelable的值為false。然後我們的create方法有設置了dialog的cancelListener和dismissListener並返回了我們創建的Dialog對象。這樣我們就獲取到了我們的Dialog對象,然後我們調用了dialog的show方法用於顯示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);
}
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;
}
try {
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
方法體的內容比較多,我們慢慢看,由於一開始mShowing變量用於表示當前dialog是否正在顯示,由於我們剛剛開始調用執行show方法,所以這裡的mShowing變量的值為false,所以if分支的內容不會被執行,繼續往下看:
if (!mCreated) {
dispatchOnCreate(null);
}
mCreated這個控制變量控制dispatchOnCreate方法只被執行一次,由於我們是第一次執行,所以這裡會執行dispatchOnCreate方法,好吧,我們看一下dispatchOnCreate方法的執行邏輯:
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
好吧,可以看到代碼的執行邏輯很簡單就是回調了Dialog的onCreate方法,那麼onCreate方法內部又執行了那些邏輯呢?由於我們創建的是AlertDialog對象,該對象繼承於Dialog,所以我們這時候需要看一下AlertDialog的onCreate方法的執行邏輯:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
可以看到這裡面除了調用super.onCreate方法之外就是調用了mAlert.installContent方法,而這裡的super.onCreate方法就是調用的Dialog的onCreate方法,Dialog的onCreate方法只是一個空的實現邏輯,所以我們具體來看一下mAlert.installContent的實現邏輯。
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
setupDecor();
}
可以看到這裡實現Window窗口的頁面設置布局初始化等操作,這裡設置了mWindow對象為NO_TITLE,然後通過調用selectContentView設置Window對象的布局文件。
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
// TODO: use layout hint side for long messages/lists
return mAlertDialogLayout;
}
可以看到這裡通過執行selectContentView方法返回布局文件的id值,這裡的默認值是mAlertDialogLayout。從這個方法開始我們就把指定布局文件的內容加載到內存中的Window對象中。我們這裡看一下具體的布局文件。
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
也就是R.layout.alert_dialog的布局文件,有興趣的同學可以看一下該布局文件的源碼,O(∩_∩)O哈哈~
繼續回到我們的installContent方法,在執行了mWindow.setContentView方法之後,又調用了setupView方法和setupDector方法,這兩個方法的主要作用就是初始化布局文件中的組件和Window對象中的mDector成員變量,這裡就不在詳細的說明。
然後回到我們的show方法,在執行了dispatchOnCreate方法之後我們又調用了onStart方法,這個方法主要用於設置ActionBar,這裡不做過多的說明,然後初始化WindowManager.LayoutParams對象,並最終調用我們的mWindowManager.addView()方法。
O(∩_∩)O哈哈~,到了這一步大家如果看了上一篇Acitivty布局繪制流程的話,就應該知道順著這個方法整個Dialog的界面就會被繪制出來了。
最後我們調用了sendShowMessage方法,可以看一下這個方法的實現:
private void sendShowMessage() {
if (mShowMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mShowMessage).sendToTarget();
}
}
這裡會發送一個Dialog已經顯示的異步消息,該消息最終會在ListenersHandler中的handleMessage方法中被執行:
private static final class ListenersHandler extends Handler {
private WeakReference mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
由於我們的msg.what = SHOW,所以會執行OnShowListener.onShow方法,那麼這個OnShowListener是何時賦值的呢?還記得我們構造AlertDialog.Builder麼?
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
}
});
這樣就為我們的AlertDialog.Builder設置了OnShowListener,可以看一下setOnShowListener方法的具體實現:
public void setOnShowListener(OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
mShowMessage = null;
}
}
這樣就為我們的Dialog中的mListenersHandler構造了Message對象,並且當我們在Dialog中發送showMessage的時候被mListenersHandler所接收。。。。
注:
這裡說一下我們平時開發中若創建的Dialog使用的Context對象不是Activity,就會報出:
Process: com.example.aaron.helloworld, PID: 11948 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:690)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:282)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:298)
at com.example.aaron.helloworld.MainActivity$1.onClick(MainActivity.java:59)
at android.view.View.performClick(View.java:4811)
at android.view.View$PerformClick.run(View.java:20136)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5552)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
的異常,這是由於WindowManager.addView方法最終會調用ViewRootImpl.setView方法,而這時候會有mToken的檢查,若我們傳入的Context對象不是Activity,這時候的mToken為空,就會出現上述問題。。。
總結:
Dialog和Activity的顯示邏輯是相似的都是內部管理這一個Window對象,用WIndow對象實現界面的加載與顯示邏輯;
Dialog中的Window對象與Activity中的Window對象是相似的,都對應著一個WindowManager對象;
Dialog相關的幾個類:Dialog,AlertDialog,AlertDialog.Builder,AlertController,AlertController.AlertParams,其中Dialog是窗口的父類,主要實現Window對象的初始化和一些共有邏輯,而AlertDialog是具體的Dialog的操作實現類,AlertDialog.Builder類是AlertDialog的內部類,主要用於構造AlertDialog,AlertController是AlertDialog的控制類,AlertController.AlertParams類是控制參數類;
構造顯示Dialog的一般流程,構造AlertDialog.Builder,然後設置各種屬性,最後調用AlertDialog.Builder.create方法獲取AlertDialog對象,並且create方法中會執行,構造AlertDialog,設置dialog各種屬性的操作。最後我們調用Dialog.show方法展示窗口,初始化Dialog的布局文件,Window對象等,然後執行mWindowManager.addView方法,開始執行繪制View的操作,並最終將Dialog顯示出來;
在前兩篇文章當中,我們主要學習了Android內存方面的相關知識,包括如何合理地使用內存,以及當發生內存洩露時如何定位出問題的原因。那麼關於內存的知識就討論到這裡,今天開
該篇為ListView下拉刷新和上拉加載實現的各種方法大合集。可能在具體的細節邏輯上處理不太到位,但基本上完成邏輯的實現。細節方面,個人可以根據自己的需求進行完善。該博客
標識符和關鍵字 基本數據類型 常量與變量 運算符 數據類型轉換 注釋及代碼編寫規范等1、標識符和關鍵字通俗的講,標識符就是程序員自己起的一個名稱(包括變量名,類名,方法名
短暫的暑假已經結束了,假期培訓正式開始。Androidmanifest.XML 清單文件es 資源文件Drawable 顏色改變Layout 布局的文件setConten