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);
}
});
}
}
一个项目别人维护了几年,其中可以借鉴的地方太多了.