日本搞逼视频_黄色一级片免费在线观看_色99久久_性明星video另类hd_欧美77_综合在线视频

國內(nèi)最全I(xiàn)T社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > php開源 > 綜合技術(shù) > Android對話框Dialog,PopupWindow,Toast的實(shí)現(xiàn)機(jī)制

Android對話框Dialog,PopupWindow,Toast的實(shí)現(xiàn)機(jī)制

來源:程序員人生   發(fā)布時(shí)間:2016-05-09 11:35:29 閱讀次數(shù):8543次

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

1.前言

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

2.Dialog實(shí)現(xiàn)的機(jī)制

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

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

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

以上是最簡單的對話框使用示例,先創(chuàng)建1個(gè)Dialog對象實(shí)例,然后Dialog加載布局,最后調(diào)用show方法來顯示該對話框,當(dāng)用戶按“back”鍵時(shí)系統(tǒng)會自動調(diào)用cancel方法來移除Dialog對話框窗口。現(xiàn)在我們就就從以上幾個(gè)進(jìn)程來詳細(xì)分析Dialog創(chuàng)建進(jìn)程。

2.1Dialog對話框創(chuàng)建

來看看Dialog類的構(gòu)造方法實(shí)現(xiàn)代碼以下:

public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { ................. //使用默許主題的構(gòu)造方法 public Dialog(Context context) { this(context, 0, true); } //指定主題的構(gòu)造方法 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; } //創(chuàng)建屬于該對話框的Context mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; } //取得Activity的窗口管理服務(wù) mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //創(chuàng)建對話框的窗口 final Window w = new PhoneWindow(mContext); mWindow = w; //設(shè)置窗口回調(diào)監(jiān)聽 w.setCallback(this); //設(shè)置窗口消失回調(diào)監(jiān)聽事件 w.setOnWindowDismissedCallback(this); //給窗口設(shè)置窗口管理器 w.setWindowManager(mWindowManager, null, null); //設(shè)置當(dāng)前對話框窗口的位置 w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } }

分析:
在Dilaog的構(gòu)造方法中主要做了以下工作:

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

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

2.2Dialog加載布局

Dialog#setContentView源碼以下:

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

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

2.3 Dialog的顯示

在創(chuàng)建完Dialog對話框以后我們僅僅調(diào)用Dialog#show方法就能夠讓該對話框顯示在當(dāng)前Activity上。

Dilaog#show源碼以下:

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

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

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

自此Dialog對話框的添加進(jìn)程已完成了,回過頭來會發(fā)現(xiàn),其實(shí)Dialog對話框窗口的創(chuàng)建添加進(jìn)程和Activity利用窗口進(jìn)程是1樣1樣的。

2.4 移除Dialog對話框

移除或隱藏對話框的代碼也很簡單。用戶僅僅調(diào)用Dialog#cancel方法就能夠移除當(dāng)前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(); }

該方法也很簡單,先發(fā)送移除Dialog時(shí)的監(jiān)聽事件,以后將操作轉(zhuǎn)發(fā)到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); } }

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

以上代碼:如果當(dāng)前線程為主線程,則調(diào)用dismissDialog方法,如果是子線程,則利用Handler將此操作發(fā)送到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(); //空方法,可以在創(chuàng)建dialog的時(shí)候重寫該方法 onStop(); //重置標(biāo)志位 mShowing = false; //處理對話框移除的監(jiān)聽事件 sendDismissMessage(); } }

分析:

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

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

2.在子線程中調(diào)用Dialog#cancel

當(dāng)子線程調(diào)用時(shí)就會履行 mHandler.post(mDismissAction)代碼。該代碼的作用就是將操作轉(zhuǎn)發(fā)到主線程中。我們看看mDismissAction的實(shí)現(xiàn)以下:

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

該類很簡單,僅僅實(shí)現(xiàn)了run回調(diào)方法,然后調(diào)用了dismissDialog方法。

2.5 Dialog 觸摸事件處理

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

響應(yīng)“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類中實(shí)現(xiàn)了按鍵事件KeyEvent.Callback接口類,因此當(dāng)有用戶按鍵輸入事件產(chǎn)生時(shí)就會調(diào)用KeyEvent.Callback接口類中的相應(yīng)方法。當(dāng)按鍵操作有“抬起”的操作行動時(shí),系統(tǒng)會調(diào)用onKeyUp方法。而Dialog類中的onKeyUp方法中會檢查當(dāng)前按鍵事件是不是為“KeyEvent.KEYCODE_BACK”事件,且當(dāng)前輸入事件沒有被取消,那末會調(diào)用onBackPressed,而該方法中判斷如果當(dāng)前對話框可以被取消則調(diào)用cancel方法來取消或隱藏當(dāng)前對話框。因此Dialog也就響應(yīng)了“Back”按鍵事件以后對話框消失。

Dialog點(diǎn)擊對話框視圖之外的地方消失

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

分析:
Dialog類一樣也實(shí)現(xiàn)了Window.Callback接口事件,同時(shí)調(diào)用Window#setCallback方法設(shè)置了該事件的回調(diào),因此Dialog也一樣具有響應(yīng)觸摸事件的功能。當(dāng)用戶點(diǎn)擊手機(jī)屏幕時(shí),就系統(tǒng)就會自動調(diào)用dispatchTouchEvent方法來分發(fā)當(dāng)前窗口的觸摸事件。該方法前后做了兩件事情:

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

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

當(dāng)用戶點(diǎn)擊Dialog窗口視圖之外的地方時(shí),最后時(shí)會履行Dialog#onTouchEvent方法的,感興趣的同學(xué)可以自行研究下!那末我們來看看Dialog#onTouchEvent方法源碼以下:

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

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

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個(gè)條件才會返回true。

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

因此有上面4個(gè)條件分析我們得知:只有當(dāng)isOutOfBounds方法返回true時(shí),條件才成立,shouldCloseOnTouch方法返回值才為true,手指導(dǎo)擊Dialog窗口以外的地方Dialog才會消失。所以主要看isOutOfBounds方法的實(shí)現(xiàn)了。

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)); }

此方法實(shí)現(xiàn)也很簡單,判斷當(dāng)前手指按下點(diǎn)擊屏幕的坐標(biāo)x,y是不是在Window窗口的視圖DecorView寬度高度以外,如果是,則返回true,否則返回false。

至此:有關(guān)Dialog響應(yīng)“Back”返回按鍵事件和點(diǎn)擊Dialog窗口以外的地方Dialog自動消失事件分析完成了。其實(shí)這1塊的原理和Activity處理“Back”返回鍵當(dāng)前Activity會調(diào)用finish方法1樣。

Dialog總結(jié):
Dialog對話框窗口Window的實(shí)現(xiàn)機(jī)制和Activity1樣。Dialog有1個(gè)Window對象,該對象屬于PhoneWindow類型用于描寫Dialog對話框窗口;PhoneWindow類有1個(gè)內(nèi)部類DecorView,用于描寫當(dāng)前窗口的頂層視圖。一樣Dialog也實(shí)現(xiàn)了Window.Callback接口回調(diào),以便Dialog也能夠處理用戶的觸摸和按鍵事件。

Dialog窗口Window視圖View層次關(guān)系圖以下:

這里寫圖片描述

3 PopupWindow彈出式對話框加載進(jìn)程

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

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

分析:
使用PopupWindow彈出式對話框主要以下幾個(gè)步驟:
1. 取得父窗口中的某1個(gè)View對象
2. 加載對話框視圖布局文件
3. 創(chuàng)建對話框?qū)嵗?
4. 顯示該對話框
5. 移除對話框

我們從PopupWindow類中的構(gòu)造方法開始分析

3.1 創(chuàng)建PopupWindow

PopupWindow構(gòu)造方法源碼以下:

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); } //設(shè)置對話框的布局 setContentView(contentView); //設(shè)置對話框的寬度 setWidth(width); //設(shè)置對話框的高度 setHeight(height); //設(shè)置對話框是不是可取得焦點(diǎn) setFocusable(focusable); }

分析:
PopupWindow構(gòu)造方法中主要做了以下幾個(gè)工作:

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

PopupWindow對話框創(chuàng)建完成,接下來看看怎樣來顯示它。

3.2 PopupWindow對話框顯示源碼分析

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

  1. showAtLocation
  2. showAsDropWown

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

PopupWindow#showAtLocation源碼以下:

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

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

同名方法showAtLocation源碼以下:

public void showAtLocation(IBinder token, int gravity, int x, int y) { //對話框正在顯示或?qū)υ捒虿季譃榭詹蛔鋈魏翁幚?/span> if (isShowing() || mContentView == null) { return; } unregisterForScrollChanged(); //重置標(biāo)記位 mIsShowing = true; mIsDropdown = false; //創(chuàng)建窗口布局參數(shù) WindowManager.LayoutParams p = createPopupLayout(token); //窗口入場動畫 p.windowAnimations = computeAnimationResource(); //對話框準(zhǔn)備工作 preparePopup(p); //對話框顯示的相對位置 if (gravity == Gravity.NO_GRAVITY) { gravity = Gravity.TOP | Gravity.START; } p.gravity = gravity; //對話框出發(fā)點(diǎn)坐標(biāo) p.x = x; p.y = y; if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; //添加對話框進(jìn)程 invokePopup(p); }

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

  1. 創(chuàng)建對話框窗口布局參數(shù)
  2. 創(chuàng)建對話框窗口的視圖
  3. 添加對話框窗口的進(jìn)程

我們順次來分析以上3步:
1.創(chuàng)建窗口參數(shù):

PopupWindow#createPopupLayoutParams源碼以下:

private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); //設(shè)置窗口默許對齊方式為最左最頂 p.gravity = Gravity.START | Gravity.TOP; //設(shè)置窗口特點(diǎn)標(biāo)記 p.flags = computeFlags(p.flags); //設(shè)置窗口類型為面板窗口即子窗口 p.type = mWindowLayoutType; //將父窗口的token標(biāo)識符賦值給子窗口 p.token = token; //設(shè)置軟輸入法模式 p.softInputMode = mSoftInputMode; //設(shè)置窗口入場動畫 p.windowAnimations = computeAnimationResource(); //設(shè)置窗口位圖格式 if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } //設(shè)置窗口寬度和高度 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; }

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

2.創(chuàng)建對話框視圖

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; } //設(shè)置窗口的陰影寬度 mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }

分析:
有以上代碼我們發(fā)現(xiàn)在創(chuàng)建對話框窗口視圖進(jìn)程中有兩種情況

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

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

當(dāng)窗口背景mBackground不為空時(shí),if條件滿足,先創(chuàng)建PopupViewContainer對象,該對象是FrameLayout類型。然后將窗口布局視圖mContentView添加到popupViewContainer視圖上,也就是PopupViewContainer類作為父類視圖來添加窗口的布局視圖mContentView。也就是當(dāng)前對話框窗口視圖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()) { //當(dāng)滿足添加back按鍵時(shí)該對話框消失 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(); //此處判斷點(diǎn)擊對話框之外的地方調(diào)用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個(gè)ViewGroup是圖組,然后你會發(fā)現(xiàn)其實(shí)該類里面并沒有實(shí)現(xiàn)甚么邏輯處理,僅僅是重寫了dispatchKeyEvent和dispatchTouch按鍵和觸摸事件分發(fā)而已。而你會發(fā)現(xiàn)在按鍵和觸摸事件方法里面處理了點(diǎn)擊PopupWindow對話框以外的像素位置時(shí),對話框調(diào)用了dismiss方法,也就是移除對話框。并且處理了按返回鍵時(shí)對話框移除的事件,一樣當(dāng)用戶按back鍵時(shí)也調(diào)用了dismiss方法。這就是為何PopupWindow在默許情況下是不響應(yīng)back事件和點(diǎn)擊對話框以外的地方PopupWindow是不消失的。所以,如果你想要你的PopupWindow類型的對話框能像Dialog1樣響應(yīng)back和點(diǎn)擊對話框之外的地方消失,你就能夠調(diào)用PopupWindow#setBackgroundDrawable方法來實(shí)現(xiàn)了。

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

3.3添加對話框窗口進(jìn)程

PopupWindow#invokePopup源碼以下:

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

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

3.4移除對話框

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

分析:
移除對話框的進(jìn)程和Dialog移除對話框相識,這里不仔細(xì)分析了。

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

PopupWindow總結(jié)

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

PopupWindow對話框窗口視圖關(guān)系以下:

這里寫圖片描述

4.Toast顯示的機(jī)制

Toast也常常使用,而且使用簡單,僅僅需要以下1行代碼便可實(shí)現(xiàn)吐司效果

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

其實(shí)分兩步調(diào)用,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; }

分析:首先調(diào)用Toast的構(gòu)造方法,然后加載Toast布局視圖,將布局視圖和Toast顯示時(shí)間參數(shù)賦值給Toast類的成員變量mNextView和mDuration。

4.2Toast構(gòu)造方法

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構(gòu)造方法也很簡單,new了1個(gè)內(nèi)部類TN,然后給TN類中的成員變量mY和mGravity賦值。那末主要的操作就在內(nèi)部類TN的構(gòu)造方法了。

Toast#TN構(gòu)造方法

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; //標(biāo)題 params.setTitle("Toast"); //窗口特點(diǎn)標(biāo)記符 params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } }

TN類的構(gòu)造方也很簡單,僅僅是創(chuàng)建了布局參數(shù)mParams并且賦值操作。

4.3Toast#show

public void show() { //Toast視圖不能為空 if (mNextView == null) { throw new RuntimeException("setView must have been called"); } //取得遠(yuǎn)程服務(wù) 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機(jī)制實(shí)現(xiàn)跨進(jìn)程通訊,此處是客戶端獲得服務(wù)的進(jìn)程 static private INotificationManager getService() { if (sService != null) { return sService; } sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; }

分析:在該方法中主要作用就是調(diào)用遠(yuǎn)程服務(wù)NotificationManagerService中的enqueueToast方法將Toast的內(nèi)部類TN對象入隊(duì)列的1個(gè)進(jìn)程。進(jìn)入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是不是是系統(tǒng)窗口 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; //判斷當(dāng)前Toast對象是不是在系統(tǒng)Toast隊(duì)列中,如果在則更新Toast的位置,將當(dāng)前Toast插入到隊(duì)列末尾。 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個(gè)利用同1時(shí)刻Toast隊(duì)列不能超過50個(gè) 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; } } } } //創(chuàng)建Toast記錄ToastRecord類的對象 record = new ToastRecord(callingPid, pkg, callback, duration); //將toast添加到Toast隊(duì)列mToastQueue mToastQueue.add(record); //取得隊(duì)列末尾位置 index = mToastQueue.size() - 1; //保持Toast所在的進(jìn)程處于激活狀態(tài) 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. //當(dāng)你全部系統(tǒng)第1次創(chuàng)建Toast添加到隊(duì)列中時(shí),index為0添加滿足 if (index == 0) { showNextToastLocked(); } } fina
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 天天操夜夜撸 | 99国产精品视频免费观看一公开 | 久久久久久久国产精品视频 | 久久精品亚洲 | 国产亚洲网站 | 亚洲一区精品视频 | 黄色在线观看网站 | 国产黄色一级片 | 欧美一区 | 亚洲aav | 久久精品国产色蜜蜜麻豆 | 日韩 国产 在线 | 在线免费精品视频 | 亚洲欧美综合色 | 亚洲精彩免费视频 | 国产精品一区二区无线 | 成人精品国产 | 亚洲区一| 九九热在线视频 | 日韩中文字幕在线视频 | av福利网| 中文字幕一区二区三区四区在线观看 | 久久国产色 | 欧美在线一区二区三区四区 | 亚洲色图25p | 精品无人乱码一区二区三区 | 一区二区三区 在线 | 蜜臀av网站| 国产在线看 | 热久久久久久 | 国产成人精品一区二区三区 | 久久99精品久久久久久噜噜 | 99re在线视频观看 | 日本亚洲精品 | 干片网 | 美女又爽又黄视频毛茸茸 | 亚洲精品福利在线 | 黄色一级大片在线免费看产 | 免费在线播放黄色网址 | 国产免费一区二区三区 | 精品久久久久久久久久久院品网 |