AlertDialog 源码分析及Bulider 模式打造万能的dialog
背景:不管我们在哪个项目中,都会用到dialog,会有不同的各种样式的dialog,Android自带的dialog采用的bulider模式,但是定制很差,往往项目中采用的dialog 都会需要一些动画,和弹出方式以及所用到的背景图片等等。这个时候我们往往采用自定义dialog,但是如果有很多不同样式的dialog,我们就需要创建好多个自定义的dialog类。这个时候就在想可不可以用一个dialog类,当然可以最简单的AlertDialog 就只有一个类,我们只需要传布局便可。
我们先看一下AlertDialog的源码,进行分析
查看源码是我们最好的学习方式
按住Ctrl右键点击查看AlertDialog的源码
public class AlertDialog extends AppCompatDialog implements DialogInterface
可以看到AlertDialog是继承AppCompatDialog类,我们来看一下这个类
可以看到AppCompatDialog extends Dialog 由此可见Android 的AlertDialog 也是一个自定义成的Dialog,至此我们的猜想是成立的,既然成立那就看一下他是怎么实现的继续看AppCompatDialog的构造函数,至于AppCompatDialog implements AppCompatCallback 这个接口我们先不考虑,官方的是这样注释的:Implemented this in order for AppCompat to be able to callback in certain situations. 有兴趣的可以自己去了解下。
private AppCompatDelegate mDelegate;
public AppCompatDialog(Context context) {
this(context, 0);
}
public AppCompatDialog(Context context, int theme) {
super(context, getThemeResId(context, theme));
// This is a bit weird, but Dialog's are typically created and setup before being shown,
// which means that we can't rely on onCreate() being called before a content view is set.
// To workaround this, we call onCreate(null) in the ctor, and then again as usual in
// onCreate().
getDelegate().onCreate(null);
// Apply AppCompat's DayNight resources if needed
getDelegate().applyDayNight();
}
protected AppCompatDialog(Context context, boolean cancelable,
OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
super.onCreate(savedInstanceState);
getDelegate().onCreate(savedInstanceState);
}
可以看到和平常的自定义dialog没什么不同,但是多了一个AppCompatDelegate这个类:
AppCompatDelegate-
This class represents a delegate which you can use to extend AppCompat’s support to any
可以看到一段官方的注释,英语不好的话就在线翻译吧–|,这句话的意思是:
此类表示一个委托,可以使用它来将AppCompat的支持扩展。也就是说这是一个委托类
从源码中我们可以看到他是将许多方法都交给了这个委托类去处理,这个类有什么作用呢?如果有谁做过夜间模式的一定知道这个类AppCompatDelegate AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 这个类有木有很熟悉,如果有不熟悉的可以去看这个链接,http://godcoder.me/2016/07/28/Android%20Material%20Design%E7%B3%BB%E5%88%97%E4%B9%8B%E5%A4%9C%E9%97%B4%E6%A8%A1%E5%BC%8F/
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Nullable
@Override
public View findViewById(@IdRes int id) {
return getDelegate().findViewById(id);
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
getDelegate().setTitle(title);
}
@Override
public void setTitle(int titleId) {
super.setTitle(titleId);
getDelegate().setTitle(getContext().getString(titleId));
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
由此可以看出,源码写的非常全面,实现了夜间和日间模式的切换,这是一个非常好的思路。
AppCompatDialog 类分析完了,说白就是我们平常自定义的dialog,只是AppCompatDialog 还实现了夜间模式
我们继续返回AlertDialog 类,接下来我们主要看这一段代码 276行
public static class Builder {
private final AlertController.AlertParams P;
private final int mTheme;
/**
* Creates a builder for an alert dialog that uses the default alert
* dialog theme.
* <p>
* The default alert dialog theme is defined by
* {@link android.R.attr#alertDialogTheme} within the parent
* {@code context}'s theme.
*
* @param context the parent context
*/
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
/**
* Creates a builder for an alert dialog that uses an explicit theme
* resource.
* <p>
* The specified theme resource ({@code themeResId}) is applied on top
* of the parent {@code context}'s theme. It may be specified as a
* style resource containing a fully-populated theme, such as
* {@link R.style#Theme_AppCompat_Dialog}, to replace all
* attributes in the parent {@code context}'s theme including primary
* and accent colors.
* <p>
很明显采用了Builder模式,我们来看一下是怎么样实现的:
看一下这个类 AlertController 从字面意思上可以看出是Alert 的控制器,我们挑一段代码查看:346行
public void setIcon(int resId) {
mIcon = null;
mIconId = resId;
if (mIconView != null) {
if (resId != 0) {
mIconView.setVisibility(View.VISIBLE);
mIconView.setImageResource(mIconId);
} else {
mIconView.setVisibility(View.GONE);
}
}
}
可以看出他是给icon赋值,也就是说这个控制器我们也可以写在Builder中,在Builder扩展赋值,也可以向源码一样写一个控制器public static class AlertParams 传参单独控制和扩展。
源码分析完毕,从源码我们可以学习到很多思想,很有益处
学到了技术,打磨我们的工具
下面我只写简单的实现方法
自定义一个Dialog
模式 布局 弹窗样式 弹出动画等逻辑和点击事件交给Builder去处理
public class CommentDialog extends Dialog {
private View mView;
private Activity context;
private WindowManager.LayoutParams lp;
//这里我将Builder自定义一个类,分开单独处理
public CommentDialog(CommentBuilder builder) {
super(builder.context);
context = (Activity) builder.context;
mView = builder.view;
}
public CommentDialog(CommentBuilder builder, int resStyle) {
super(builder.context, resStyle);
context = (Activity) builder.context;
mView = builder.view;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mView);
setCanceledOnTouchOutside(false);
Window window = getWindow();
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
window.getDecorView().setPadding(0, 0, 0, 0);
lp = window.getAttributes();
lp.gravity = Gravity.CENTER; //弹出位置
window.setAttributes(lp);
}
}
Builder 类
public class CommentBuilder {
public Context context;
public View view;
private int resStyle = -1;
public boolean cancelTouchout;
public CommentBuilder(Context context) {
this.context = context;
}
/**
* 布局
*
* @param resView
* @return
*/
public CommentBuilder view(int resView) {
view = LayoutInflater.from(context).inflate(resView, null);
return this;
}
/**
* dialog样式
*
* @param resStyle
* @return
*/
public CommentBuilder style(int resStyle) {
this.resStyle = resStyle;
return this;
}
/**
* 添加一个点击事件
*
* @param viewRes
* @param listener
* @return
*/
public CommentBuilder addViewOnclick(int viewRes, View.OnClickListener listener) {
view.findViewById(viewRes).setOnClickListener(listener);
return this;
}
/**
* 触摸dialog外部是否可以取消
*
* @param val false 不可dismiss
* @return
*/
public CommentBuilder cancelTouchout(boolean val) {
cancelTouchout = val;
return this;
}
public CommentDialog build() {
if (resStyle != -1) {
return new CommentDialog(this, resStyle);
} else {
return new CommentDialog(this);
}
}
}
如何使用呢
CommentBuilder builder = new CommentBuilder(CropperActivity.this);
builder.view(R.layout.dialog_comment_upload_success_layout)
.style(R.style.shareStyles)
.setReview(R.id.dcus_tv, isReview)
.addViewOnclick(R.id.ll_dialog_ok, CropperActivity.this);
commentDialog = builder.build();
commentDialog.show();
注:对于不同样式的Dialog,我们只需要传不同的布局和样式,赋值和改变值还可以在Builder中扩展增加对应的方法;点击事件我们可以写在当前的Activity 和 Fragment onClick()中实现,若想添加多个点击事件 可以添加多个.addViewOnclick(R.id.XX, CropperActivity.this);我们还可以学习源码一样,写一个控制器进行控制,具体大家可以自己实现。