高仿iOS的BlurDialog
原文連結:http://www.jianshu.com/p/1e2d68183c3c
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。
本文主要討論Dialog的blur背景與泛談window中的各種view。
Github上有許多仿iOS的dialog,但是都沒有讓背景模糊,第三方的view太坑,不如自己對著iOS模擬器的圖與動畫做出來。耗費了很久時間終於搞定了,如下圖,左邊是仿iOS的dialog,右邊是SupportV7的dialog。
BlurDialog
Dialog 生命週期的簡要分析
在建構函式時,建立了一個window,並設定了相關window屬性。
當呼叫 show()
onCreate
與 onStart
(都是一些無關緊要的方法),最後通過
WindowManager
添加了 window 中的 DecoView,從這裡開始,view就要開始走繪製流程了。
當呼叫 dismiss()
時,通過 WindowManager
移除了 View,並呼叫onStop()
執行可能的清理任務。
@see #Dialog (setUp window inside)
@see #show()
- @see #onCreate(Bundle) (call setContentView here)
- @see #onStart()
- mWindowManager.addView(mDecor, l);
@see #dismiss()
- mWindowManager.removeViewImmediate(mDecor);
- @see #onStop() (clean work)
如果我們需要自定義一個dialog,只需要在 show()
之前配置好 window 的屬性,以及在 onCreate()
中將自定義的 xml 佈局初始化即可。
在
mWindowManager.addview()
之前,除非指定了 view 的dp高寬,否則輸出的view高度等資料將是0,-1之類的值。為了獲得準確的值,可以使用view.post()
將訊息入隊,這樣可以獲取到正確的資料。
BlurAlertDialog 的實現
通過post入隊即可,入隊主要是為了準確測量偏移量。
//初始化 BlurDrawable 的樣式
//`mRvFragCard`表示在底層的將要被模糊的View
BlurDrawable drawable = new BlurDrawable(mRvFragCard);
dialog.getWindow().getDecorView().post(new Runnable() {
@Override public void run() {
//設定邊緣圓弧的 dp
drawable.setCornerRadius(xxDp);
// 設定繪製偏移量
// 座標為相對整個螢幕,dialog最左邊,最上面的點
drawable.setDrawOffset(x,y);
dialog.getWindow().setBackgroundDrawable(drawable);
}
});
dialog.show();
注意不能用系統自帶的dialog,因為它的內部是不開放的(也就是紅色的程式碼),window建立流程也不透明,可能會丟擲異常。
示例地址
本部分完,需要ui的直接clone即可。
以下為冗長的理論資料
Window 與 WindowManager
-
window是對整個view的一套管理,對開發者來說僅僅是介面。
PhoneWindow
是對window
的抽象方法的實現,手機ROM廠商可以修改或者實現它,而開發者只能反射呼叫PhoneWindow
的一些功能。 -
WindowManager是系統服務,負責view繪製。它對開發中看來也只是一個介面,通過
getSystemService
即可獲得。
介面由ROM實現
/system/framework/framework.jar
反編譯jar後,可以獲得物件 android.view.WindowManagerGlobal
,具體流程可以看這裡,我們只需要知道這個函式涉及到AIDL通訊,並且它是非同步回撥的即可。
在國產ROM常見的狀態列字型變色,懸浮Activity等特色功能,都是由
WindowManager
負責繪製的,通過在 Window 中寫入某些flag,之後重繪的時候就能顯示出獨有的樣式。
Dialog 下的 Window 佈局
此場景適用於最原始的 Activity 與 Dialog ,它們的 ViewTree 佈局如下
DecoView
DecorContentParent
ContentView(android.R.id.content)
你寫的xml佈局
通過 Dump 工具可以驗證:
Dump
1. DecoView
在 PhoneWindow
中,它的變數名為 mDecor
, 它是 window 中的 top-level 的view,通過
installDecor()
進行構造,它預設的大小是 wrap_content
,我們通常所說的
window.setXXX()
本質上呼叫的就是 DecoView(與它的子view)
網上有部分文章說
DecoView
不是top-level 的 View,這個說法是錯的,各位可以手動設定 id 測試
2. DecorContentParent
在 PhoneWindow
中,它的變數名為 mDecorContentParent
,通過它可以設定
Window
中的Title,requestWindowFeature
等功能,它是一個寬泛的層,層次可能是一層(在NoActionBar,notitle的條件下),可能有多個層,上圖就有3層。在實際開發中,我們一般也用不到(如果非要用的話,還是隻能反射呼叫)。
舉個例子,在最新的 AppCompatActivity 中,它重寫的setContentView
中通過 ensureSubDecor()
方法手動填充幾個夾層View,這個算一個應用案例
@Override
public void setContentView(int resId) {
//對mWindow.setContentView()的一道包裝
//新增部分View, 比如Actionbar/Title/Toolbar
ensureSubDecor();
.....
}
3. ContentView
在 PhoneWindow
中,它的變數名為 mContentParent
。我們日常接觸的就是
ContentView
了,它包裝著我們寫的xml佈局
contentView可以通過ID獲得,這個id是內建的,如下兩個是等價的
android.R.id.content
Window.ID_ANDROID_CONTENT
4. xml佈局
我們自己寫的xml佈局,需要注意 inflate 方法中的boolean值,它控制當前view是否依附到 parent 中。
如果view本身需要被add時(比如dialog,activity),呼叫setContentView()
即可,它將生成view並依附到parent中。
如果這個view並沒有parent(比如viewholder), 選擇不依附即可
inflater.inflate(ResId, null, false);
總結
- 儘量在setcontentView之前呼叫window屬性
- 涉及到長寬等內容時,因為view的顯示是非同步的,所以需要post傳送,以免獲得到的結果為0