DialogUtil項目地址
這個項目將近1k star,項目快一年沒更新了.
簡要流程分析
StyledDialog.buildLoading("加載中...")
流程梳理.
主要是用來創建ConfigBean對象, 該對象描述了下面創建Dialog對象時候的一些要求.
// StyledDialog.java
public class StyledDialog {
public static ConfigBean buildLoading(CharSequence msg) {
// 創建ConfigBean對象,先往下看.
return DialogAssigner.getInstance().assignLoading(null, msg, true, false);
}
public static Handler getMainHandler() {
// 這個方法就是獲取一個可以向主線程發送消息的Handler對象
if (mainHandler == null) {
mainHandler = new Handler(Looper.getMainLooper());
}
return mainHandler;
}
// init()是在APP剛啓動的時候創建的.
public static void init(Context context) {
// 他是ApplicationContext對象.
StyledDialog.context = context;
// 創建唯一的可以向主線程發送消息的Handler對象
mainHandler = new Handler(Looper.getMainLooper());
DefaultConfig.initBtnTxt(context);
}
}
// DialogAssigner.java
public class DialogAssigner implements Assignable {
// 獲取DialogAssigner對象單例
public static DialogAssigner getInstance(){
if (instance == null){
instance = new DialogAssigner();
}
return instance;
}
@Override
public ConfigBean assignLoading(Context context, CharSequence msg, boolean cancelable, boolean outsideTouchable) {
// 該方法用來創建ConfigBean對象,對ConfigBean中的變量進行了一些設置.
ConfigBean bean = new ConfigBean();
bean.context = context;// 設置上下文
bean.msg = msg;
bean.type = DefaultConfig.TYPE_IOS_LOADING;// 設置Dialog的類型(自己定義的類型)
bean.cancelable = cancelable;// 設置是否可以取消等等
bean.outsideTouchable = outsideTouchable;
return bean;
}
}
ConfigBean.show()
public class ConfigBean extends MyDialogBuilder implements Styleable {
@Override
public Dialog show() {
if (Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId()) {
// 如果調用show(),是在主線程中調用的, 那麼直接調用showInMainThread().
return showInMainThread();
}
//說明不是主線程,需要做處理
final CountDownLatch latch = new CountDownLatch(1);
final Dialog[] dialog = new Dialog[1];
// StyledDialog.getMainHandler() 獲取一個可以向主線程發消息的Handler對象
StyledDialog.getMainHandler().post(new Runnable() {
@Override
public void run() {
// 如果不是在主線程中調用的show(),那麼將showInMainThread()使用主線程調用.
dialog[0] = showInMainThread();
latch.countDown();
}
});
...
return dialog[0];
}
private Dialog showInMainThread() {
Tool.fixContext(this);// 該方法就是對ConfigBean對象中的Context變量進行檢查賦值.
....
// buildByType():該方法存在於父類MyDialogBuilder中,做了很多的事情.
// 這個方法下面有分析
buildByType(this);
...
if (dialog != null && !dialog.isShowing()) {
// dialog這個參數就是在buildByType()中創建的Dialog對象了.
Tool.showDialog(dialog, this);
return dialog;
} else if (alertDialog != null && !alertDialog.isShowing()) {
Tool.showDialog(alertDialog, this);
return alertDialog;
}
return null;
}
}
// MyDialogBuilder.java
public class MyDialogBuilder {
protected ConfigBean buildByType(ConfigBean bean) {
switch (bean.type) {
...
case DefaultConfig.TYPE_IOS_LOADING:
Tool.newCustomDialog(bean);// 創建Dialog對象,該方法.
buildLoading(bean);// 爲Dialog對象填充內容.
break;
...
}
...
Window window = dialog.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// 根據ConfigBean對象中的設置爲Dialog設置進出場動畫
Tool.setWindowAnimation(window, bean);
// 根據ConfigBean對象中的設置Dialog是否可以取消
Tool.setCancelable(bean);
// 監聽Dialog的消失與顯示,對Dialog與對應的Activity做相應的管理.
Tool.setListener(dialog, bean);
// 這個方法裏面會設置Dialog風格,還有根據返回或者設置按鈕事件將Dialog消失的邏輯.
Tool.adjustStyle(bean);
return bean;
}
protected ConfigBean buildLoading(ConfigBean bean) {
// 該方法用來爲Dialog對象填充內容.
View root = View.inflate(bean.context, R.layout.loading, null);
ImageView gifMovieView = (ImageView) root.findViewById(R.id.iv_loading);
AnimationDrawable drawable = (AnimationDrawable) gifMovieView.getDrawable();
if (drawable != null) {
drawable.start();
}
TextView tvMsg = (TextView) root.findViewById(R.id.loading_msg);
tvMsg.setText(bean.msg);
bean.dialog.setContentView(root);
return bean;
}
}
Tool.showDialog()
// Tool.java
public class Tool {
public static void showDialog(final Dialog dialog, final ConfigBean bean) {
// 這裏是將Dialog放到主線程中show().
StyledDialog.getMainHandler().post(new Runnable() {
@Override
public void run() {
try {
// dialog 只有show出來之後菜可以調整窗體大小.
dialog.show();
// 調整窗體大小
adjustWindow(dialog, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 該方法是用來調整窗體大小的
private static void adjustWindow(final Dialog dialog, final ConfigBean bean) {
// addOnGlobalLayoutListener當視圖可見性改變時候,將回調onGlobalLayout該方法.
dialog.getWindow().getDecorView().getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 設置BootmSheetDialog的一些屬性
setBottomSheetDialogPeekHeight(bean);
// 調整寬高
adjustWH(dialog, bean);
// 移除監聽
dialog.getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
// 該方法調整Dialog窗體寬高
public static void adjustWH(Dialog dialog, ConfigBean bean) {
if (dialog == null) {
return;
}
// 獲取Dialog窗體
Window window = dialog.getWindow();
// 獲取Dialog 中的DecorView對象
View rootView = window.getDecorView();
// 獲取Dialog對應Window的參數
WindowManager.LayoutParams wl = window.getAttributes();
// 獲取屏幕寬高
int width = window.getWindowManager().getDefaultDisplay().getWidth();
int height = window.getWindowManager().getDefaultDisplay().getHeight();
// 獲取Dialog中的DecorView寬高
int measuredHeight = rootView.getMeasuredHeight();
int measuredWidth = rootView.getMeasuredWidth();
float widthRatio = 0.85f;
float heightRatio = 0f;
...
// 通知WindowManager重新layout佈局
dialog.onWindowAttributesChanged(wl);
}
public static void setBottomSheetDialogPeekHeight(final ConfigBean bean) {
// dialog類型是BottomSheetDialog纔會執行該方法
if (bean.hasBehaviour && bean.dialog instanceof BottomSheetDialog) {
View view = bean.dialog.getWindow().findViewById(android.support.design.R.id.design_bottom_sheet);
if (view == null) {
return;
}
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
// 如果是隱藏的話
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
Tool.dismiss(bean);
// 這個設置是BottomSheetDialog消失以後再能夠重新show()的關鍵.
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
if (bean.bottomSheetDialogMaxHeightPercent > 0f && bean.bottomSheetDialogMaxHeightPercent < 1f) {
// 通過百分比,獲取BottomSheetDialog的最大高度.
int peekHeight = (int) (bean.bottomSheetDialogMaxHeightPercent * ScreenUtil.getScreenHeight());
// 將高度設置給BottomSheetBehavior
bottomSheetBehavior.setPeekHeight(peekHeight);
}
}
}
// 該方法就是對ConfigBean對象中的Context變量進行檢查賦值.
public static ConfigBean fixContext(ConfigBean bean) {
Activity activity1 = null;
if (!(bean.context instanceof Activity)) {
// 如果Context不是Activity,那麼就取應用中最上層的一個Activity對象當作ConfigBean對象的Context變量.
Activity activity = ActivityStackManager.getInstance().getTopActivity();
if (isUsable(activity)) {// 檢查這個Activity是否可以使用.
activity1 = activity;
}
} else {
// 如果傳入的ConfigBean對象中的context就是Activity對象就檢查這個Activity是否可以使用.
Activity activity = (Activity) bean.context;
if (isUsable(activity)) {
activity1 = activity;
}
}
if (activity1 != null) {
// 將Activity對象賦值給ConfigBean對象中的context.
bean.context = activity1;
} else {
// 如果Activity對象不存在, 就將ApplicationContext對象賦值給ConfigBean對象中的context.
bean.context = StyledDialog.context;
}
return bean;
}
public static ConfigBean newCustomDialog(ConfigBean bean) {
// 創建Dialog對象
Dialog dialog = new Dialog(bean.context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
bean.dialog = dialog;
return bean;
}
// 該方法是根據Dialog顯示或者消失對Dialog與對應的Activity做一些管理.
public static void setListener(final Dialog dialog, final ConfigBean bean) {
if (dialog == null) {
return;
}
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog0) {
...
bean.listener.onShow();
//將Activity與dialog保管起來用map與set集合.
DialogsMaintainer.addWhenShow(bean.context, dialog);
if (bean.type == DefaultConfig.TYPE_IOS_LOADING || bean.type == DefaultConfig.TYPE_MD_LOADING) {
// 將activity與dialog保存在另一個Map集合中.
DialogsMaintainer.addLoadingDialog(bean.context, dialog);
}
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog0) {
if (bean.type == DefaultConfig.TYPE_IOS_INPUT) {
IosAlertDialogHolder iosAlertDialogHolder = (IosAlertDialogHolder) bean.viewHolder;
if (iosAlertDialogHolder != null) {
iosAlertDialogHolder.hideKeyBoard();
}
}
if (bean.listener != null) {
bean.listener.onCancle();
}
}
});
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog0) {
...
// 這裏是將Dialog與對應的Activity從Map集合中刪除掉
DialogsMaintainer.removeWhenDismiss(dialog);
if (bean.type == DefaultConfig.TYPE_IOS_LOADING || bean.type == DefaultConfig.TYPE_MD_LOADING) {
DialogsMaintainer.dismissLoading(dialog);
}
}
});
}
// 調整一些風格
public static void adjustStyle(final ConfigBean bean) {
// 設置背景
setBg(bean);
// 設置窗口的暗淡
setDim(bean);
Dialog dialog = bean.dialog == null ? bean.alertDialog : bean.dialog;
Window window = dialog.getWindow();
window.setGravity(bean.gravity);
if (bean.context instanceof Activity) {
//setHomeKeyListener(window,bean);
} else {
// 這裏其實就是爲當創建的Dialog中context是Application Context的時候所做的事情
// 設置窗體的類型爲toast類型, 因爲toast類型是不需要token的,他是系統的
window.setType(WindowManager.LayoutParams.TYPE_TOAST);
// 獲取Dialog窗體的的參數
WindowManager.LayoutParams params = window.getAttributes();
if (params == null) {
// 如果沒有參數就設置一個
// 其實這個不可能爲null的, 源碼中知道每次創建窗體都會默認創建一個WindowManager.LayoutParams對象出來的.
params = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
// 設置位圖格式
params.format = PixelFormat.RGBA_8888;
params.flags =
// WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
params.dimAmount = 0.2f;
// 爲窗體設置參數
window.setAttributes(params);
// 爲窗體設置回調返回按鍵的回調監聽
window.getDecorView().setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK || event.getKeyCode() == KeyEvent.KEYCODE_SETTINGS) {
StyledDialog.dismiss(bean.alertDialog, bean.dialog);
return true;
}
return false;
}
});
// 監聽Home案件廣播?
setHomeKeyListener(window, bean);
window.setDimAmount(0.2f);
}
}
}
總結點
- Dialog通過Handler發送消息到UI線程執行顯示操作.
public static void showDialog(final Dialog dialog, final ConfigBean bean) {
// StyledDialog.getMainHandler()獲取一個可以發送消息到主線程的Handler對象
StyledDialog.getMainHandler().post(new Runnable() {
@Override
public void run() {
try {
dialog.show();
adjustWindow(dialog, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
- 動態調整Dialog大小
private static void adjustWindow(final Dialog dialog, final ConfigBean bean) {
//監聽視圖可見性改變時候的回調
dialog.getWindow().getDecorView().getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 視圖可見後,再來調整寬高
adjustWH(dialog, bean);
// 當更新完成Dialog寬高之後移除監聽
dialog.getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
// 該方法調整Dialog窗體寬高
public static void adjustWH(Dialog dialog, ConfigBean bean) {
if (dialog == null) {
return;
}
Window window = dialog.getWindow();
View rootView = window.getDecorView();
WindowManager.LayoutParams wl = window.getAttributes();
int width = window.getWindowManager().getDefaultDisplay().getWidth();
int height = window.getWindowManager().getDefaultDisplay().getHeight();
int measuredHeight = rootView.getMeasuredHeight();
int measuredWidth = rootView.getMeasuredWidth();
float widthRatio = 0.85f;
float heightRatio = 0f;
wl.height = (int) (height * heightRatio);
wl.height = measuredHeight;
// 通知WindowManager重新對Dialog進行佈局
dialog.onWindowAttributesChanged(wl);
}
- 當傳入的Context不存在或者不能用的時候,會自動找尋第一個Activity作爲Context(通過
Tool.fixContext()
).
// BaseApp.java
public class BaseApp extends Application {
@Override
public void onCreate() {
super.onCreate();
...
StyledDialog.init(getApplicationContext());
registCallback();
...
}
private void registCallback() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// 當Activity啓動時, 將該Activity對象存入堆中
ActivityStackManager.getInstance().addActivity(activity);
}
...
@Override
public void onActivityPaused(Activity activity) {
// 當該Activity暫停的時候, 動態的將該Activity對應的沒有現實的Dialog刪除
DialogsMaintainer.onPause(activity);
}
...
@Override
public void onActivityDestroyed(Activity activity) {
// 刪除Activity對應的Dailog對象,從set集合與Map集合中.
ActivityStackManager.getInstance().removeActivity(activity);
}
});
}
}
一個項目別人維護了幾年,其中可以借鑑的地方太多了.