1. 程式人生 > >原始碼分析Dialog自定義大小無效坑

原始碼分析Dialog自定義大小無效坑

最近在專案中用到了自定義Dialog,以前也是經常用,只不過要麼是用自帶的dialog樣式,要麼也是很簡單的佈局,所以並沒有重視修改dialog大小的坑。直到這次專案中產(keng)品(die)經(wan)理(yi)死了都說dialog大小別扭要求改,然後突然發現自己自定義的dialog的大小不能調整後整個人都驚呆了,因此打算深入原始碼看看解決這個問題。本文分析的原始碼均來自Android API 24。

demo的自定義佈局如下:

首先想著通過修改dialog的Window來進行修改,程式碼如下:

開開心心執行一下:

em…..emmmm???尼瑪?這啥玩意兒??還是沒變?再怎麼4000的大小也不會是這麼個小玩意兒把?鬱悶之極….(我太仁慈大小給4000太小了?)
又繼續試驗,不斷的調整各種大小,還是沒效果!最後求助網路後發現,設定視窗大小的程式碼必須放在.show()之後才能有效果,程式碼修改如下:


執行後檢視效果:

成功設定了大小!開心得不得了!!!

然而百思不得其解為什麼必須要在show之後才能顯示??按照正常邏輯不應該在show之前設定大小???
在網上找半天也沒找到前人們分析原始碼,所以決定自己硬著頭皮看看原始碼,點進show()方法後進行,一看大有文章,先上程式碼:

show方法中的程式碼居然不是我想象中的一兩行程式碼,而是那麼多!首先在上圖中標註1處,因為我們是建立新的dialog,所以會執行dispatchOnCreate(null)這個方法。顧名思義,呼叫建立,這裡面肯定大有文章,猜測是進行了Dialog的建立,我們跟進去檢視具體原始碼。

跟進去後發現裡面呼叫了onCreate()方法,在這裡是不是有種似曾相識的感覺?怎麼感覺和Activity差不多了呢?按捺住基動的內心我們跟進去繼續看看。因為Dialog的onCreate()方法實現為空的,所以我們選擇一個子類來進行檢視,我們進入AlertDialog中檢視:


我們發現呼叫了mAlet.installContent()方法,我們來看看這是什麼東東。AlertController是一個AlertDialog的控制類,包括在建立AlertDialog時(程式碼如下):
AlertDialog mTextDialog = new AlertDialog.Builder(context).setTitle(“溫馨提示”).setView(view) .create();

在這裡使用的Builder模式中,setTitle之類的設定引數也是先將引數設定給AlertController.Param。具體在這裡就暫時不管。
我們繼續檢視installContent()方法:


先設定dialog的contentView,然後呼叫setContentView()方法。是不是越來越熟悉這個套路!繼續忍著壓抑砰砰直跳的小內心,繼續跟進去看看。

我們看到,在Dialog類中我們發現它最終呼叫了Window的setContentView()方法;而Window在Android中只有PhoneWindow這一個實現類。 接下來的工作就是和Activity中的步驟一模一樣,因為都是呼叫Window的setContentView()方法;具體分析可看我記錄的另一篇文章內容(傳送門:http://www.jianshu.com/p/28bbb6778593)。
通過setContentView()方法後,建立了Dialog的decorView,並且將我們的自定義佈局加入到decorView中。其中在這個過程中,mWindowAttributes的高度和寬度在mWindow.setContentView()中的installDecor()中的generateLayout()中被修改,在這裡貼出修改部分的程式碼:
修改視窗寬高呼叫程式碼

setLayout()具體程式碼

在上面圖中的標註處呼叫了setLayout(int width,int height)方法,這裡傳入了自適應的常量方式。而在setLayout方法中首先獲取window的LayoutParams,然後修改了寬高。因此在這裡過後,Window的mWindowAttributes將會重新改變,因此導致了我們在show之前修改的mWindowAttributes值將被覆蓋,因此失效!
接下來把debug模式下錯誤程式碼的引數貼出來看看:
如下圖為debug下show之前設定WindowManager.LayoutParams的值,可以看到我們把window的寬高設定為1143.

下圖為show方法內執行過了dispatchOnCreate()方法後的WindowManager.LayoutParams的值.

可以看出就算之前設定引數,隨後在show方法中也會被覆蓋,因此在show之前設定引數無效。
所以我們因此只需要在show()之後設定即可。這樣就可以設定Dialog佈局大小。而呼叫dialog.getWindow().setAttributes(params);這句話中,會觸發Window引數變化的介面,從而使得dialog重新設定自身大小。程式碼如下:

至此我們就將設定Dialog的Window寬高沒有效果的原因分析完畢。