多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > Android對話框Dialog,PopupWindow,Toast的實現機制

Android對話框Dialog,PopupWindow,Toast的實現機制

來源:程序員人生   發布時間:2016-05-09 11:35:29 閱讀次數:8522次

【轉載請注明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】

1.前言

在Android系統中窗口(Window)分3種類型:利用窗口,子窗口,系統窗口。上1篇博客分析了Android利用窗口Window的創建進程,接下來這篇博客來學習其他兩種窗口類型的實現機制。Android開發中常常會使用到Dialog,PopupWindow,Toast等對話框來作為提示信息或和用戶交互。但是這些對話框其實都是窗口,它們的創建和移除機制也就是Android系統對窗口的添加和刪除的進程了。
這篇博客從源碼角度來分析Dialog,PopupWindow,Toast的實現原理。

2.Dialog實現的機制

在Android系統中Dialog對話框是子窗口,也就是Dialog對話框窗口必須要有1個父窗口,那末Dialog對話框窗口的父窗口是誰呢?我不說相信大家也知道了吧!沒錯就是Activity利用窗口,為何呢?這篇博客來為你解答!

相信很多人平日里用的最多的對話框還是AlertDialog,不過今天它可不是主角,Dialog才是我們今天的重點。其實AlertDialog只是Google官方定制了很多不同主題不同布局的Dialog而已,AlertDialog繼承自Dialog類。因此我們只分析Dialog的實現機制。使用對話框都是在Activity中,因此在Activity中創建最簡單的Dialog對話框代碼以下:

Dialog dialog = new Dialog(MainActivity.this); dialog.setContentView(R.layout.dialog); dialog.show(); //取消對話框 dialog.cancel();

以上是最簡單的對話框使用示例,先創建1個Dialog對象實例,然后Dialog加載布局,最后調用show方法來顯示該對話框,當用戶按“back”鍵時系統會自動調用cancel方法來移除Dialog對話框窗口?,F在我們就就從以上幾個進程來詳細分析Dialog創建進程。

2.1Dialog對話框創建

來看看Dialog類的構造方法實現代碼以下:

public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { ................. //使用默許主題的構造方法 public Dialog(Context context) { this(context, 0, true); } //指定主題的構造方法 Dialog(Context context, int theme, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (theme == 0) { TypedValue outValue = new TypedValue(); //使用默許的對話框主題 context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } //創建屬于該對話框的Context mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; } //取得Activity的窗口管理服務 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); } }

分析:
在Dilaog的構造方法中主要做了以下工作:

  • 根據參數createContextThemeWrapper的值來決定是使用參數theme指定的主題還是使用其父窗口Activity的主題。
  • 調用Context#getSystemService方法取得當前利用的窗口管理器WindowManager對象,有上1篇博客知道:1個利用不管有多少個Activity都只有1個WindowManager對象用于管應當前利用中的所有窗口。
  • 為Dialog對話框創建1個窗口Window對象,Window是個抽象類,其實現指向PhoneWindow類。
  • 給窗口設置事件回調監聽,由于在Dialog類中實現了Window#Callback接口類,該接口類目的是讓Dialog對話框的窗口具有處理響應按鍵觸摸事件的能力,這也就是為何用戶默許創建的Dialog對話框可以響應“Back”回退按鍵事件和點擊對話框窗口之外的地方Dialog對話框會自動消失隱藏。由此可知,Dialog和Activity都實現了消息處理。
  • 設置Window類的內部成員變量值WindowManager,由此知道Window的WindowManager和Dilaog的WindowManager指向同1個對象。
  • 設置當前Dialog窗口的對齊方式為居中,這就是為何我們默許的對話框都是居中顯示了吧。
  • 創建對話框的事件監聽對象,用于對話框顯示,消失,取消時的1些監聽操作。

Dialog內部創建了1個Window對象,窗口是1個抽象的東西,和Activity利用窗口1樣,需要往窗口Window中添加視圖View來顯示內容。因此調用setContentView方法來加載對話框的布局視圖。

2.2Dialog加載布局

Dialog#setContentView源碼以下:

public void setContentView(int layoutResID) { mWindow.setContentView(layoutResID); }

分析:
該方法將操作轉發給Window類中的setContentView方法,但是mWindow對象是指向PhoneWindow類的,也就是調用PhoneWindow類中的setContentView方法。到此處我們發現Dialog加載布局的流程和Activity加載布局的流程是1樣的。因此這里就不仔細分析了,可以參考上1篇博客。到此,Dialog對話框窗口Window內部就已添加了視圖DecorView了。那末剩下的事就是Dilaog對話框怎樣顯示在手機屏幕上了。

2.3 Dialog的顯示

在創建完Dialog對話框以后我們僅僅調用Dialog#show方法就能夠讓該對話框顯示在當前Activity上。

Dilaog#show源碼以下:

public void show() { //如果當前對話框正在顯示時僅僅做1些簡單可見度設置操作 if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } //設置dialog是不是已取消標志 mCanceled = false; if (!mCreated) { dispatchOnCreate(null); } //是個空方法,可以在創建Dialog時重寫該方法 onStart(); //得到Dialog對話框窗口的頂層視圖DecorView mDecor = mWindow.getDecorView(); //設置窗口actionbar 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 { //重點 添加對話框窗口的頂層視圖到Activity上 mWindowManager.addView(mDecor, l); //重置對話框狀態 mShowing = true; //異步消息處理機制來處理Dialog對話框顯示時候的1個回調監聽 sendShowMessage(); } finally { } }

分析:
在show方法里主要做了以下幾件工作:

  • 判斷當前Dialog對話框窗口是存在,如果存在直接讓其顯示便可;如果當前窗口不存在,則調用Dialog的回調方法onCreate方法,用戶可以在onCreate回調方法中創建1個新的Dialog對話框。
  • 取得Dialog對話框的頂層視圖DecorView對象賦值給成員變量mDecor用于addView方法的參數。
  • 根據條件為當前對話框窗口設置導航欄logo圖標等。
  • 取得當前窗口的參數屬性賦值給l,用于addView方法的參數。
  • 調用WindowManager#addView方法添加Dialog對話框窗口。

自此Dialog對話框的添加進程已完成了,回過頭來會發現,其實Dialog對話框窗口的創建添加進程和Activity利用窗口進程是1樣1樣的。

2.4 移除Dialog對話框

移除或隱藏對話框的代碼也很簡單。用戶僅僅調用Dialog#cancel方法就能夠移除當前Activity之上的對話框了。

public void cancel() { if (!mCanceled && mCancelMessage != null) { mCanceled = true; // Obtain a new message so this dialog can be re-used Message.obtain(mCancelMessage).sendToTarget(); } dismiss(); }

該方法也很簡單,先發送移除Dialog時的監聽事件,以后將操作轉發到dismiss方法中。

/** * Dismiss this dialog, removing it from the screen. This method can be * invoked safely from any thread. Note that you should not override this * method to do cleanup when the dialog is dismissed, instead implement * that in {@link #onStop}. */ @Override public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) {//主線程 dismissDialog(); } else {//子線程 mHandler.post(mDismissAction); } }

分析:
注釋解釋的很清楚了:該方法可以安全的在任何線程中調用,也就是說可以在子線程中移除對話框而不報錯。Looper.myLooper()方法取得的Looper對象是當前線程的Looper,而mHandler.getLooper()方法取得的Looper對象是mHandler所在線程的Looper。由于Android系統規定只要有關UI操作都必須在主線程中,而我們在創建Dialog是在主線程中,mHandler對象是在主線程中創建的,因此mHandler.getLooper()就是主線程的Looper。

以上代碼:如果當前線程為主線程,則調用dismissDialog方法,如果是子線程,則利用Handler將此操作發送到UI線程中操作。

1.在主線程中移除對話框

void dismissDialog() { //如果對話框的頂層視圖不存在或dialog沒有正在顯示則不做任何處理 if (mDecor == null || !mShowing) { return; } //如果對話框窗口已燒毀也不做任何處理 if (mWindow.isDestroyed()) { Log.e(TAG, "Tried to dismissDialog() but the Dialogs window was already destroyed!"); return; } try { //移除對話框 mWindowManager.removeViewImmediate(mDecor); } finally { if (mActionMode != null) { mActionMode.finish(); } mDecor = null; mWindow.closeAllPanels(); //空方法,可以在創建dialog的時候重寫該方法 onStop(); //重置標志位 mShowing = false; //處理對話框移除的監聽事件 sendDismissMessage(); } }

分析:

  • 如果當前Dialog窗口的視圖DecorView為空或當前窗口不存在,則不做任何處理,直接退出當前方法便可。
  • 如果當前Dialog窗口已被燒毀了也不做任何處理。
  • 調用WindowManager#removeView方法來移除當前對話框窗口。

該方法主要作用就是從Activity的窗口管理器mWindowManager中移除對話框窗口的視圖,也就是完成了該對話框的移除操作。

2.在子線程中調用Dialog#cancel

當子線程調用時就會履行 mHandler.post(mDismissAction)代碼。該代碼的作用就是將操作轉發到主線程中。我們看看mDismissAction的實現以下:

private final Runnable mDismissAction = new Runnable() { public void run() { dismissDialog(); } };

該類很簡單,僅僅實現了run回調方法,然后調用了dismissDialog方法。

2.5 Dialog 觸摸事件處理

我們知道Dialog默許是響應“Back”返回鍵當前對話框消失事件和點擊Dialog對話框視圖之外的地方當前對話框也會消失,而默許的PopupWindow對話框是不支持以上兩種事件操作的。那末為何會是這樣呢?此處先分析Dialog對觸摸事件的處理,下1節分PopupWindow不支持事件處理的緣由。

響應“Back”返回鍵

public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { ........ public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { onBackPressed(); return true; } return false; } ........ public void onBackPressed() { if (mCancelable) { cancel(); } } }

分析:
在Dialog類中實現了按鍵事件KeyEvent.Callback接口類,因此當有用戶按鍵輸入事件產生時就會調用KeyEvent.Callback接口類中的相應方法。當按鍵操作有“抬起”的操作行動時,系統會調用onKeyUp方法。而Dialog類中的onKeyUp方法中會檢查當前按鍵事件是不是為“KeyEvent.KEYCODE_BACK”事件,且當前輸入事件沒有被取消,那末會調用onBackPressed,而該方法中判斷如果當前對話框可以被取消則調用cancel方法來取消或隱藏當前對話框。因此Dialog也就響應了“Back”按鍵事件以后對話框消失。

Dialog點擊對話框視圖之外的地方消失

public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { ........ public boolean dispatchTouchEvent(MotionEvent ev) { //響應窗口的觸摸事件分發 if (mWindow.superDispatchTouchEvent(ev)) { return true; } //響應Dialog的觸摸事件 return onTouchEvent(ev); } ........ }

分析:
Dialog類一樣也實現了Window.Callback接口事件,同時調用Window#setCallback方法設置了該事件的回調,因此Dialog也一樣具有響應觸摸事件的功能。當用戶點擊手機屏幕時,就系統就會自動調用dispatchTouchEvent方法來分發當前窗口的觸摸事件。該方法前后做了兩件事情:

  1. 先調用Dialog的窗口Window對象的方法Window#superDispatchTouchEvent來處理觸摸按鍵事件。
  2. 如果Window窗口的觸摸按鍵事件處理返回為false,則調用Dialog#onTouchEvent方法來繼續處理觸摸按鍵事件。

有關觸摸事件傳遞機制請參考這篇博客:Android事件分發機制完全解析,帶你從源碼的角度完全理解(上)。

當用戶點擊Dialog窗口視圖之外的地方時,最后時會履行Dialog#onTouchEvent方法的,感興趣的同學可以自行研究下!那末我們來看看Dialog#onTouchEvent方法源碼以下:

public boolean onTouchEvent(MotionEvent event) { if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) { cancel(); return true; } return false; }

分析:
該方法也很簡單,如果if條件滿足,則直接調用cancel方法來取消當前對話框,if條件不滿足時不做任何處理直接返回。那末我們來看看甚么情況下if添加滿足致使了調用cancel方法取消對話框。必須滿足3個條件:當前對話框可以被取消,對話框正在顯示,和Window.shouldCloseOnTouch方法返回true。前兩個條件默許都滿足,那末來看看第3個條件甚么情況下滿足吧!

Window.shouldCloseOnTouch源碼以下:

/** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; } return false; }

分析:
該方法需要滿足4個條件才會返回true。

  • 布爾變量mCloseOnTouchOutside:表示是不是支持點擊窗口之外的地方窗口可消失。Dialog對話框的窗口默許支持,也就是該條件滿足。如果想修改該條件,你可以調用Dialog#setCanceledOnTouchOutside(false)方法來到達點擊窗口之外的地方Dialog消失,其實終究是設置mCloseOnTouchOutside變量為false,然后致使shouldCloseOnTouch方法返回false。
  • 當前觸摸事件是不是為“MotionEvent.ACTION_DOWN”手指按下事件,自然滿足。
  • 調用isOutOfBounds方法判斷當前手指導擊的坐標是不是在Dialog對話框窗口視圖以外?
  • 當前Dialog對話框窗口是不是添加了視圖DecorView?如果對話框顯示出來了,自然窗口DecorView對象不為空。

因此有上面4個條件分析我們得知:只有當isOutOfBounds方法返回true時,條件才成立,shouldCloseOnTouch方法返回值才為true,手指導擊Dialog窗口以外的地方Dialog才會消失。所以主要看isOutOfBounds方法的實現了。

Window#isOutOfBounds源碼以下:

private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth()+slop)) || (y > (decorView.getHeight()+slop)); }

此方法實現也很簡單,判斷當前手指按下點擊屏幕的坐標x,y是不是在Window窗口的視圖DecorView寬度高度以外,如果是,則返回true,否則返回false。

至此:有關Dialog響應“Back”返回按鍵事件和點擊Dialog窗口以外的地方Dialog自動消失事件分析完成了。其實這1塊的原理和Activity處理“Back”返回鍵當前Activity會調用finish方法1樣。

Dialog總結:
Dialog對話框窗口Window的實現機制和Activity1樣。Dialog有1個Window對象,該對象屬于PhoneWindow類型用于描寫Dialog對話框窗口;PhoneWindow類有1個內部類DecorView,用于描寫當前窗口的頂層視圖。一樣Dialog也實現了Window.Callback接口回調,以便Dialog也能夠處理用戶的觸摸和按鍵事件。

Dialog窗口Window視圖View層次關系圖以下:

這里寫圖片描述

3 PopupWindow彈出式對話框加載進程

開發中用的最多的對話框AlertDialog,如果需要定制自己的對話框風格或AlertDialog沒法滿足你的需求時,就能夠斟酌下PopupWindow對話框了。彈出式對話框PopupWinsow的使用也很簡單,僅僅調用已下幾行代碼就可以實現最簡單的對話框了!

//取得父窗口視圖中的某個View對象 View parentView = findViewById(R.id.main); //加載popupWindow對話框布局 View popWindow = LayoutInflater.from(MainActivity.this).inflat(R.layout.dialog, null); //創建對話框 PopupWindow pw = new PopupWindow(popWindow,ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true); //顯示對話框 pw.showAtLocation(parentView, Gravity.CENTER, 0, 0); //移除對話框 pw.dismiss();

分析:
使用PopupWindow彈出式對話框主要以下幾個步驟:
1. 取得父窗口中的某1個View對象
2. 加載對話框視圖布局文件
3. 創建對話框實例
4. 顯示該對話框
5. 移除對話框

我們從PopupWindow類中的構造方法開始分析

3.1 創建PopupWindow

PopupWindow構造方法源碼以下:

public PopupWindow(View contentView, int width, int height, boolean focusable) { if (contentView != null) { //取得所依賴窗口(父窗口)的context對象 mContext = contentView.getContext(); //取得所依賴窗口的WindowManager對象 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } //設置對話框的布局 setContentView(contentView); //設置對話框的寬度 setWidth(width); //設置對話框的高度 setHeight(height); //設置對話框是不是可取得焦點 setFocusable(focusable); }

分析:
PopupWindow構造方法中主要做了以下幾個工作:

  • 取得父窗口的context對象,也就是當前Activity的Context對象,然后有context對象取得全部利用的WindowManager對象,從上1篇博客知道:1個利用只有唯逐一個WindowManager對象用于管理全部利用的窗口。
  • 設置對話框布局,該操作主要是將對話框視圖賦值給PopupWindow類的成員變量mContentView。
  • 分別設置對話框布局的寬度,高度,和取得 焦點的能力。這3個方法的主要操作還是對PopupWindow類中的成員變量mWidth,mHeight,mFocusable賦值,以便對話框顯示的時候使用。

PopupWindow對話框創建完成,接下來看看怎樣來顯示它。

3.2 PopupWindow對話框顯示源碼分析

PopupWindow對話框顯示的方法有兩種:

  1. showAtLocation
  2. showAsDropWown

其實這兩種方法實現的原理是相同的,僅僅是顯示的位置控制不1樣而已,因此這里就分析其中1個方法showAtLocation實現的原理。

PopupWindow#showAtLocation源碼以下:

public void showAtLocation(View parent, int gravity, int x, int y) { showAtLocation(parent.getWindowToken(), gravity, x, y); }

該方法僅僅將操作轉發給同名方法,只是利用第1個參數parent來取得父窗口的標識符token對象,但是,父窗口Window視圖中的任何1個View得到的標識符都是同1個對象。因此在構建parent參數的時候只要滿足1個條件就能夠了:那就是參數parent只要是對話框所依賴的父類窗口中的其中1個子View便可,也就是Activity布局中的任何1個子View都可以作為PopupWindow類中showAtLocation方法的第1個參數。

同名方法showAtLocation源碼以下:

public void showAtLocation(IBinder token, int gravity, int x, int y) { //對話框正在顯示或對話框布局為空不做任何處理 if (isShowing() || mContentView == null) { return; } unregisterForScrollChanged(); //重置標記位 mIsShowing = true; mIsDropdown = false; //創建窗口布局參數 WindowManager.LayoutParams p = createPopupLayout(token); //窗口入場動畫 p.windowAnimations = computeAnimationResource(); //對話框準備工作 preparePopup(p); //對話框顯示的相對位置 if (gravity == Gravity.NO_GRAVITY) { gravity = Gravity.TOP | Gravity.START; } p.gravity = gravity; //對話框出發點坐標 p.x = x; p.y = y; if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; //添加對話框進程 invokePopup(p); }

以上方法主要做了3件事:

  1. 創建對話框窗口布局參數
  2. 創建對話框窗口的視圖
  3. 添加對話框窗口的進程

我們順次來分析以上3步:
1.創建窗口參數:

PopupWindow#createPopupLayoutParams源碼以下:

private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); //設置窗口默許對齊方式為最左最頂 p.gravity = Gravity.START | Gravity.TOP; //設置窗口特點標記 p.flags = computeFlags(p.flags); //設置窗口類型為面板窗口即子窗口 p.type = mWindowLayoutType; //將父窗口的token標識符賦值給子窗口 p.token = token; //設置軟輸入法模式 p.softInputMode = mSoftInputMode; //設置窗口入場動畫 p.windowAnimations = computeAnimationResource(); //設置窗口位圖格式 if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } //設置窗口寬度和高度 if (mHeightMode < 0) { p.height = mLastHeight = mHeightMode; } else { p.height = mLastHeight = mHeight; } if (mWidthMode < 0) { p.width = mLastWidth = mWidthMode; } else { p.width = mLastWidth = mWidth; } return p; }

分析:
該方法主要是設置對話框的 gravity(對齊方式),flag(窗口特點),type(窗口類型),softInputMode (軟輸入法模式),windowAnimations(窗口相干動畫),width,height等參數。

2.創建對話框視圖

API22 PopupWindow#preparePopup源碼以下:

private void preparePopup(WindowManager.LayoutParams p) { //處理異常情況 if (mContentView == null || mContext == null || mWindowManager == null) { throw new IllegalStateException("You must specify a valid content view by " + "calling setContentView() before attempting to show the popup."); } //窗口背景不為空 if (mBackground != null) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); int height = ViewGroup.LayoutParams.MATCH_PARENT; if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { height = ViewGroup.LayoutParams.WRAP_CONTENT; } PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else {//背景窗口為空 mPopupView = mContentView; } //設置窗口的陰影寬度 mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }

分析:
有以上代碼我們發現在創建對話框窗口視圖進程中有兩種情況

  • 窗口背景mBackground不為空。
  • 窗口背景mBackground為空。

而我們知道在PopupWindow類中成員變量mBackground默許是為空的,只有調用setBackgroundDrawable方法才能修改mBackground成員變量的值,也就是為PopupWindow對話框設置背景。

當窗口背景mBackground不為空時,if條件滿足,先創建PopupViewContainer對象,該對象是FrameLayout類型。然后將窗口布局視圖mContentView添加到popupViewContainer視圖上,也就是PopupViewContainer類作為父類視圖來添加窗口的布局視圖mContentView。也就是當前對話框窗口視圖mContentView外面還包裹著1成PopupViewContainer。那末我們來看看PopupViewContainer做了甚么工作。

private class PopupViewContainer extends FrameLayout { private static final String TAG = "PopupWindow.PopupViewContainer"; public PopupViewContainer(Context context) { super(context); } @Override protected int[] onCreateDrawableState(int extraSpace) { if (mAboveAnchor) { // 1 more needed for the above anchor state final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); return drawableState; } else { return super.onCreateDrawableState(extraSpace); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { //處理back按鍵事件 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (getKeyDispatcherState() == null) { return super.dispatchKeyEvent(event); } if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { //當滿足添加back按鍵時該對話框消失 dismiss(); return true; } } return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } //處理對話框觸摸事件 @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); //此處判斷點擊對話框之外的地方調用dismiss來隱藏對話框 if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); return true; } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } else { return super.onTouchEvent(event); } } @Override public void sendAccessibilityEvent(int eventType) { // clinets are interested in the content not the container, make it event source if (mContentView != null) { mContentView.sendAccessibilityEvent(eventType); } else { super.sendAccessibilityEvent(eventType); } } } }

PopupViewContainer 繼承自FrameLayout是1個ViewGroup是圖組,然后你會發現其實該類里面并沒有實現甚么邏輯處理,僅僅是重寫了dispatchKeyEvent和dispatchTouch按鍵和觸摸事件分發而已。而你會發現在按鍵和觸摸事件方法里面處理了點擊PopupWindow對話框以外的像素位置時,對話框調用了dismiss方法,也就是移除對話框。并且處理了按返回鍵時對話框移除的事件,一樣當用戶按back鍵時也調用了dismiss方法。這就是為何PopupWindow在默許情況下是不響應back事件和點擊對話框以外的地方PopupWindow是不消失的。所以,如果你想要你的PopupWindow類型的對話框能像Dialog1樣響應back和點擊對話框之外的地方消失,你就能夠調用PopupWindow#setBackgroundDrawable方法來實現了。

當用戶沒有設置窗口背景也就是沒有調用PopupWindow#setBackgroundDrawable方法時mBackground為空,那末當前窗口的視圖就直接是mContentView了。但是所有View默許的按鍵和觸摸事件是沒有處理back事件和點擊對話框以外的地方對話框消失的處理的。因此,使用PopupWindow對話框不設置對話框背景時是不響應“back”返回按鍵和點擊窗口以外的地方消失的。

3.3添加對話框窗口進程

PopupWindow#invokePopup源碼以下:

private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) { p.packageName = mContext.getPackageName(); } mPopupView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); mWindowManager.addView(mPopupView, p); }

分析:
該方法也很簡單,主要是調用了WindowManager#addView方法來添加對話框視圖。從而PopupWindow對話框顯示在Activity利用窗口之上了。

3.4移除對話框

public void dismiss() { //只有當對話框正在顯示且對話框視圖不為空 if (isShowing() && mPopupView != null) { //重置標志位 mIsShowing = false; unregisterForScrollChanged(); try { 從Activity上移除對話框視圖 mWindowManager.removeViewImmediate(mPopupView); } finally { if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { //移除其子View ((ViewGroup) mPopupView).removeView(mContentView); } mPopupView = null; //設置對話框移除時的監聽事件 if (mOnDismissListener != null) { mOnDismissListener.onDismiss(); } } } }

分析:
移除對話框的進程和Dialog移除對話框相識,這里不仔細分析了。

自此PopupWindow對話框的創建,添加,移除的進程已分析完成了。其主要流程就是取得當前利用程序的WindowManager對象,然后將對話框的視圖添加到WindowManager上來顯示PopupWindow對話框,調用WindowManager#remove方法移除對話框視圖來到達移除當前對話框。所以PopupWindow類型的對話框必須要依附在某1個Activity之上,也就是PopupWindow是1個子窗口。

PopupWindow總結

Dialog對話框和PopupWindow對話框最主要的區分就是Dialog窗口內部具有1個PhoneWindow對象來處理了輸入事件,而PopupWindow窗口內部沒有PhoneWindow對象來理輸入事件。這也就致使了Dialog能響應“Back”返回鍵對話框消失和點擊對話框以外的地方對話框消失而PopupWindow不能的緣由。

PopupWindow對話框窗口視圖關系以下:

這里寫圖片描述

4.Toast顯示的機制

Toast也常常使用,而且使用簡單,僅僅需要以下1行代碼便可實現吐司效果

Toast.makeText(MainActivity.this, "Toast", Toast.LENGTH_SHORT).show();

其實分兩步調用,Toast#makeText,Toast#show。

4.1Toast#makeText

public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); result.mNextView = v; result.mDuration = duration; return result; }

分析:首先調用Toast的構造方法,然后加載Toast布局視圖,將布局視圖和Toast顯示時間參數賦值給Toast類的成員變量mNextView和mDuration。

4.2Toast構造方法

public Toast(Context context) { mContext = context; mTN = new TN(); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); }

Toast構造方法也很簡單,new了1個內部類TN,然后給TN類中的成員變量mY和mGravity賦值。那末主要的操作就在內部類TN的構造方法了。

Toast#TN構造方法

private static class TN extends ITransientNotification.Stub { private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); ...... TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; //Toast高度 params.height = WindowManager.LayoutParams.WRAP_CONTENT; //Toast寬度 params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; //Toast入場動畫 params.windowAnimations = com.android.internal.R.style.Animation_Toast; //Toast窗口類型 params.type = WindowManager.LayoutParams.TYPE_TOAST; //標題 params.setTitle("Toast"); //窗口特點標記符 params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } }

TN類的構造方也很簡單,僅僅是創建了布局參數mParams并且賦值操作。

4.3Toast#show

public void show() { //Toast視圖不能為空 if (mNextView == null) { throw new RuntimeException("setView must have been called"); } //取得遠程服務 INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } } ......... private static INotificationManager sService; //利用AIDL機制實現跨進程通訊,此處是客戶端獲得服務的進程 static private INotificationManager getService() { if (sService != null) { return sService; } sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; }

分析:在該方法中主要作用就是調用遠程服務NotificationManagerService中的enqueueToast方法將Toast的內部類TN對象入隊列的1個進程。進入NotificationManagerService類的enqueueToast方法看看

public class NotificationManagerService extends SystemService { ............. private final IBinder mService = new INotificationManager.Stub() { @Override public void enqueueToast(String pkg, ITransientNotification callback, int duration) { if (pkg == null || callback == null) { Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); return ; } //判斷Toast是不是是系統窗口 final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { if (!isSystemToast) { Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); return; } } synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); try { ToastRecord record; //判斷當前Toast對象是不是在系統Toast隊列中,如果在則更新Toast的位置,將當前Toast插入到隊列末尾。 int index = indexOfToastLocked(pkg, callback); // If its already in the queue, we update it in place, we dont // move it to the end of the queue. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); } else { // Limit the number of toasts that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. //此處限制1個利用同1時刻Toast隊列不能超過50個 if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); for (int i=0; ifinal ToastRecord r = mToastQueue.get(i); if (r.pkg.equals(pkg)) { count++; if (count >= MAX_PACKAGE_NOTIFICATIONS) { Slog.e(TAG, "Package has already posted " + count + " toasts. Not showing more. Package=" + pkg); return; } } } } //創建Toast記錄ToastRecord類的對象 record = new ToastRecord(callingPid, pkg, callback, duration); //將toast添加到Toast隊列mToastQueue mToastQueue.add(record); //取得隊列末尾位置 index = mToastQueue.size() - 1; //保持Toast所在的進程處于激活狀態 keepProcessAliveLocked(callingPid); } // If its at index 0, its the current toast. It doesnt matter if its // new or just been updated. Call back and tell it to show itself. // If the callback fails, this will remove it from the list, so dont // assume that its valid after this. //當你全部系統第1次創建Toast添加到隊列中時,index為0添加滿足 if (index == 0) { showNextToastLocked(); } } fina
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 性刺激欧美三级在线观看 | 日韩久久一区二区三区 | 国产一区二区三区樱花动漫 | 成人毛片一区二区三区 | 中文字幕资源在线 | 黄色毛片视频网站 | 欧洲一区二区三区 | 日韩欧美小说 | 999热成人精品国产免 | 欧美精品影院 | 国产成人综合手机在线播放 | 日本怡春院欧美一区二区三区 | 欧美军人男同69gay | 50岁老女人毛片一级亚洲 | 国内精品免费视频 | 国产福利第一视频 | 亚洲精品毛片久久久久久久 | 成 人 a v免费视频 | 亚洲精品图 | 国产欧美一区二区精品久久久 | 久久se精品一区精品二区 | 黑人40厘米全进去xxxx猛交 | 日本视频中文字幕 | 亚洲精品播放 | aⅴ天堂网| 亚洲精品视频久久久 | 美美女高清毛片视频黄的一免费 | 欧美在线播放成人免费 | 久久不卡 | free性m.freesex欧美 | 欧美一区二区三区视频 | 国产高清在线精品一区a | julia一区福利视频在线观看 | 在线观看成年人免费视频 | 欧美一区二区三区久久综合 | jizz中国zz女人18 | 97精品一区二区三区在线不卡 | 欧美一区二区三区四区视频 | 自拍视频网站 | 亚洲一级高清在线中文字幕 | 国产欧美精品一区二区三区四区 |