Android DialogUtil 源碼閱讀

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);
        }
    }
}
總結點
  1. 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();
            }
        }
    });
}
  1. 動態調整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);
}
  1. 當傳入的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);
                }
            });
        }
}

一個項目別人維護了幾年,其中可以借鑑的地方太多了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章