在我们的日常开发中,对话框是一个常见的组件,例如下面的对话框,分别是三种不同类型的对话框
在Android开发中,对话框也和我们的TitleBar一样,有各种样式,而且它比TitleBar更加的复杂,因为对话框显示的位置还有底部显示,中心显示,顶部显示,以及动画等,因此。对于对话框,我们也可以封装以下。这里我们还是采用Builder模式来封装。
封装思路:将UI实现及事件和我们对话框基本属性进行解耦,从而一套对话框框架可以实现多种布局及UI实现。
封装的第一版的对话框架构图如下:
BaseDialog:
采用Builder模式来封装,将各种参数都封装到DialogParams这个类中
mParams 主要是对话框在屏幕切换方向后重建时使用的
BaseDialog.Builder:
提供对对话框的各种参数的设置,最后以Builder模式创建对话框。
DialogParams:
对话框参数类,主要包括对话框的宽高,布局方向,是否可取消,内容View以及监听事件等
DialogViewHolder:
辅助类,用于对对话框中的内容View进行简单的Text设置,图片设置,事件设置等
DialogListener:
事件监听器
这个版本目前还不是非常成熟,不过已经能引入项目使用了,后续还有优化的空间。
先来看BaseDialog.Builder的实现
/**
* 使用builder模式
*/
public static class Builder{
private Context mBuilderContext;
/**
* 参数
*/
private DialogParams mBuilderParams ;
/**
* helper
*/
private DialogViewHolder mHolder;
/**
* 所引用的dialog
*/
BaseDialog dialog;
/**
* 构造方法
* @param context
*/
public Builder(Context context){
mBuilderContext = context;
dialog = new BaseDialog();
mBuilderParams = new DialogParams();
mHolder = new DialogViewHolder(dialog);
mBuilderParams.mHolder = mHolder;
mBuilderParams.mEventMap.put(TEXT,new HashMap());
mBuilderParams.mEventMap.put(DRAWABLE,new HashMap());
mBuilderParams.mEventMap.put(LISTENER,new HashMap());
}
/**
* 设置setContentView
* @param layoutId
* @return
*/
public Builder setContentView(int layoutId){
mBuilderParams.mLayoutId = layoutId;
mBuilderParams.mContentView = LayoutInflater.from(mBuilderContext).inflate(mBuilderParams.mLayoutId,null);
LogManager.i(TAG,"mBuilderParams.mContentView.getParent():" + mBuilderParams.mContentView.getParent());
mBuilderParams.mHolder.setContentView(mBuilderParams.mContentView);
return this;
}
/**
* 设置ContentView
* @param view
* @return
*/
public Builder setContentView(View view){
mBuilderParams.mContentView = view;
mBuilderParams.mLayoutId = 0;
mBuilderParams.mHolder.setContentView(mBuilderParams.mContentView);
LogManager.i(TAG,"mBuilderParams.mContentView.getParent():" + mBuilderParams.mContentView.getParent());
return this;
}
/**
* 设置点击对话框外是否可以取消
* @param cancelable
* @return
*/
public Builder setCancelable(boolean cancelable){
mBuilderParams.isCancelable = cancelable;
return this;
}
/**
* 设置要显示需要的fragment
* @param manager
* @return
*/
public Builder setFragmentManager(FragmentManager manager){
mBuilderParams.mFragmentManager = manager;
return this;
}
/**
* 全部宽度显示
* @return
*/
public Builder fullWidth(){
mBuilderParams.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
return this;
}
/**
* 设置位置 居中 底部 顶部
* @param gravity
* @return
*/
public Builder setGravity(int gravity){
mBuilderParams.mGravity = gravity;
return this;
}
/**
* 设置内容
* @param viewId
* @param text
*/
public Builder setText(int viewId, CharSequence text) {
LogManager.i(TAG,"setText viewId : " + viewId + ",text :" + text);
mBuilderParams.mEventMap.get(TEXT).put(viewId,text);
mBuilderParams.mHolder.setText(viewId,text);
return this;
}
/**
* 设置ImageView
* @param viewId
* @param bitmap
*/
public Builder setImage(int viewId, Bitmap bitmap) {
LogManager.i(TAG,"setImage viewId : " + viewId + ",bitmap :" + bitmap);
mBuilderParams.mEventMap.get(DRAWABLE).put(viewId,bitmap);
mBuilderParams.mHolder.setImage(viewId,bitmap);
return this;
}
/**
* 设置ImageView的Drawable
* @param viewId
* @param resId
*/
public Builder setDrawable(int viewId, int resId) {
LogManager.i(TAG,"setDrawable viewId : " + viewId + ",resId :" + resId);
mBuilderParams.mEventMap.get(DRAWABLE).put(viewId,resId);
Drawable drawable ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
drawable = mBuilderContext.getDrawable(resId);
}else {
drawable = mBuilderContext.getResources().getDrawable(resId);
}
mBuilderParams.mHolder.setDrawable(viewId,drawable);
return this;
}
/**
* 设置ImageView的Drawable
* @param viewId
* @param drawable
*/
@Deprecated
public Builder setDrawable(int viewId, Drawable drawable) {
LogManager.i(TAG,"setDrawable viewId : " + viewId + ",drawable :" + drawable);
mBuilderParams.mHolder.setDrawable(viewId,drawable);
return this;
}
/**
* 设置点击事件
*
* @param viewId
* @param listener
*/
public Builder setDialogListener(int viewId, DialogListener listener) {
LogManager.i(TAG,"setOnClickListener viewId : " + viewId + ",listener :" + listener);
mBuilderParams.mEventMap.get(LISTENER).put(viewId,listener);
mBuilderParams.mHolder.setOnClickListener(viewId,listener);
return this;
}
/**
* 创建对话框
* @return
*/
public BaseDialog build(){
dialog.mParams = mBuilderParams;
return dialog;
}
}
Builder主要提供各种设置各种参数接口给外部,其他的没有什么特别的
接下来看:DialogParams
/**
* @author Created by qiyei2015 on 2017/5/13.
* @version: 1.0
* @email: [email protected]
* @description: Dialog的控制类,控制其中的ContentView等的操作
*/
public class DialogParams implements Serializable{
/**
* Dialog辅助类
*/
public DialogViewHolder mHolder;
/**
* 显示DialogFragment需要的FragmentManager
*/
public FragmentManager mFragmentManager;
/**
* 宽度
*/
public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 动画
*/
public int mAnimations = 0;
/**
* 位置
*/
public int mGravity = Gravity.CENTER;
/**
* 高度
*/
public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 内容View
*/
public View mContentView;
/**
* 内容布局id
*/
public int mLayoutId;
/**
* 是否可以取消
*/
public boolean isCancelable;
/**
* 记录事件的map,这里的事件包括setText,D监听器,图片等
*/
public Map> mEventMap = new HashMap<>();
@Override
public String toString() {
return "DialogParams{" +
"mHolder=" + mHolder +
", mFragmentManager=" + mFragmentManager +
", mWidth=" + mWidth +
", mAnimations=" + mAnimations +
", mGravity=" + mGravity +
", mHeight=" + mHeight +
", mContentView=" + mContentView +
", mLayoutId=" + mLayoutId +
", isCancelable=" + isCancelable +
'}';
}
}
主要是对话框的,宽,高,布局位置,布局文件及内容View,是否可取消等
接下来看DialogViewHolder
/**
* @author Created by qiyei2015 on 2017/5/13.
* @version: 1.0
* @email: [email protected]
* @description: Dialog辅助类
*/
public class DialogViewHolder {
private static final String TAG = DialogViewHolder.class.getSimpleName();
/**
* 所引用的dialog
*/
private BaseDialog mDialog;
/**
* 内容view
*/
private View mContentView;
/**
* contentView中的view集合,使用弱引用,防止内存泄漏
*/
private SparseArray> mViews;
public DialogViewHolder(BaseDialog dialog){
mViews = new SparseArray<>();
mDialog = dialog;
}
/**
* 根据id获取对应的view
* @param viewId
* @param
* @return
*/
public T getView(int viewId){
WeakReference viewWeakReference = mViews.get(viewId);
View view = null;
if (viewWeakReference != null){
view = viewWeakReference.get();
}
if (view == null){
view = mContentView.findViewById(viewId);
if (view != null){
mViews.put(viewId,new WeakReference(view));
}
}
return (T) view;
}
/**
* 设置文本内容
* @param viewId
* @param text
*/
public void setText(int viewId,CharSequence text){
TextView tv = getView(viewId);
LogManager.i(TAG,"setText tv : " + tv + ",text:" + text);
if (tv != null){
tv.setText(text);
LogManager.i(TAG,"setText tv.getText --> " + tv.getText().toString());
}
}
/**
* 设置ImageView的图片
* @param viewId
* @param bitmap
*/
public void setImage(int viewId, Bitmap bitmap){
View view = getView(viewId);
LogManager.i(TAG,"setImage view : " + view + ",bitmap:" + bitmap);
if (view != null && view instanceof ImageView){
((ImageView)view).setImageBitmap(bitmap);
}
//如果drawable不为null,应该让view显示出来
if (bitmap != null){
view.setVisibility(View.VISIBLE);
}
}
/**
* 设置Drawable
* @param viewId
* @param drawable
*/
public void setDrawable(int viewId, Drawable drawable){
View view = getView(viewId);
LogManager.i(TAG,"setDrawable view : " + view + ",resId:" + drawable);
if (view == null){
return;
}
//如果drawable不为null,应该让view显示出来
if (drawable != null){
view.setVisibility(View.VISIBLE);
}
//如果是ImageView就设置图片,否则就设置View背景
if (view instanceof ImageView){
((ImageView)view).setImageDrawable(drawable);
}else {
view.setBackground(drawable);
}
}
/**
* 给某个view设置点击事件
* @param viewId
* @param listener
*/
public void setOnClickListener(int viewId, final DialogListener listener){
View view = getView(viewId);
LogManager.i(TAG,"setOnClickListener view : " + view + ",listener:" + listener);
if (view != null){
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(v);
mDialog.dismiss();
}
});
}
}
/**
* @return {@link #mContentView}
*/
public View getContentView() {
return mContentView;
}
/**
* @param contentView the {@link #mContentView} to set
*/
public void setContentView(View contentView) {
mContentView = contentView;
}
}
这里使用了弱引用,防止内存泄漏
好,最后来看看BaseDialog的实现:
/**
* @author Created by qiyei2015 on 2017/5/13.
* @version: 1.0
* @email: [email protected]
* @description:
*/
public class BaseDialog extends DialogFragment {
/**
* 调试标志
*/
private static final String TAG = BaseDialog.class.getSimpleName();
/**
* context
*/
protected Context mContext;
/**
* Dialog的参数
*/
protected DialogParams mParams;
/**
* onSaveInstanceState保存的key
*/
private static final String KEY = "dialog";
/**
* Text的key
*/
private static final String TEXT = "text";
/**
* Drawable的key
*/
private static final String DRAWABLE = "drawable";
/**
* Listener的Key
*/
private static final String LISTENER = "listener";
/**
* 是否是恢复的数据
*/
private boolean isSavedInstanceState = false;
/**
* 构造方法
*/
public BaseDialog(){
super();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.dialog);
//保存数据,防止重建Dialog时出现数据丢失的情况
if (savedInstanceState != null){
mParams = (DialogParams) savedInstanceState.getSerializable(KEY);
LogManager.i(TAG,"savedInstanceState mParams:" + mParams.toString());
isSavedInstanceState = true;
}
setCancelable(mParams.isCancelable);
LogManager.i(TAG,"onCreate()");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(KEY,mParams);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
LogManager.i(TAG,"onCreateView()");
//去除标题
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
//必须返回的View是Builder中已经设置的那个对象,使用LayoutInflater加载的是另外一个对象
View contentView = mParams.mContentView;
if (savedInstanceState != null){
contentView = LayoutInflater.from(getContext()).inflate(mParams.mLayoutId,null);
mParams.mContentView = contentView;
}
LogManager.i(TAG,"contentView.getParent():" + contentView.getParent());
return contentView;
}
@Override
public void onStart() {
super.onStart();
//设置布局属性
Window window = getDialog().getWindow();
window.setLayout(mParams.mWidth,mParams.mHeight);
window.setGravity(mParams.mGravity);
LogManager.i(TAG,"onStart()");
if (!isSavedInstanceState){
return;
}
HashMap hashMap = mParams.mEventMap.get(TEXT);
for (Map.Entry entry : hashMap.entrySet()){
TextView view = (TextView) mParams.mContentView.findViewById(entry.getKey());
view.setText((CharSequence) entry.getValue());
}
hashMap = mParams.mEventMap.get(DRAWABLE);
for (Map.Entry entry : hashMap.entrySet()){
ImageView view = (ImageView) mParams.mContentView.findViewById(entry.getKey());
view.setVisibility(View.VISIBLE);
if (entry.getValue() instanceof Integer){
view.setImageResource((Integer) entry.getValue());
}else if (entry.getValue() instanceof Bitmap){
view.setImageBitmap((Bitmap) entry.getValue());
}
}
hashMap = mParams.mEventMap.get(LISTENER);
for (final Map.Entry entry : hashMap.entrySet()){
View view = mParams.mContentView.findViewById(entry.getKey());
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((DialogListener) entry.getValue()).onClick(v);
dismiss();
}
});
}
}
//略过Builder定义
/**
* 根据id获取对应的view
* @param viewId
* @param
* @return
*/
public T getView(int viewId){
return mParams.mHolder.getView(viewId);
}
/**
* 显示对话框
*/
public void show(){
//如果没有被添加
if (!isAdded()){
FragmentTransaction transaction = mParams.mFragmentManager.beginTransaction();
transaction.add(this, TAG);
transaction.commitAllowingStateLoss();
}
LogManager.i(TAG,"show()");
}
/**
* 取消显示对话框
*/
@Override
public void dismiss(){
super.dismiss();
}
}
没啥好说的,就是注意做了一个在屏幕旋转时恢复对话框参数的动作
目前第一版设计就是这样了,详细的代码参考我的github
https://github.com/qiyei2015/EssayJoke SDK 下的dialog目录
BaseDialog的使用很简单,下面有两个例子:
BaseDialog dialog = new BaseDialog.Builder(this)
.setCancelable(true)
//.setContentView(R.layout.dialog_test)
.setContentView(R.layout.dialog_test)
.setText(R.id.dialog_content,"这是一个对话框,哈哈哈!")
.setDialogListener(R.id.dialog_ok, new DialogListener() {
@Override
public void onClick(View v) {
ToastUtil.showLongToast("对话框点击了确认");
}
})
.setDialogListener(R.id.dialog_cancel, new DialogListener() {
@Override
public void onClick(View v) {
ToastUtil.showLongToast("对话框点击了取消");
}
})
// .setGravity(Gravity.BOTTOM)
// .fullWidth()
.setFragmentManager(getSupportFragmentManager())
.build();
dialog.show();
效果如下:
例子2:
BaseDialog dialog = new BaseDialog.Builder(this)
.setCancelable(true)
.setContentView(R.layout.common_dialog)
//.setContentView(contentView)
.setText(R.id.id_dialog_title,"这是一个对话框标题!")
.setText(R.id.id_tv_content,"对话框内容")
.setText(R.id.id_tv_confirm,"确认")
.setText(R.id.id_tv_cancel,"取消")
.setDrawable(R.id.id_dialog_title_imv,R.drawable.icon_login_single)
.setDialogListener(R.id.id_tv_confirm, new DialogListener() {
@Override
public void onClick(View v) {
ToastUtil.showLongToast("对话框点击了确认");
}
})
.setDialogListener(R.id.id_tv_cancel, new DialogListener() {
@Override
public void onClick(View v) {
ToastUtil.showLongToast("对话框点击了取消");
}
})
// .setGravity(Gravity.BOTTOM)
// .fullWidth()
.setFragmentManager(getSupportFragmentManager())
.build();
dialog.show();
效果如下:
后续再对该框架进行优化