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

一个项目别人维护了几年,其中可以借鉴的地方太多了.

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