您的位置:首页 > 产品设计 > UI/UE

Android View 高级框架二 Builder模式打造通用对话框

2017-11-04 15:10 405 查看

1 前言

在我们的日常开发中,对话框是一个常见的组件,例如下面的对话框,分别是三种不同类型的对话框







在Android开发中,对话框也和我们的TitleBar一样,有各种样式,而且它比TitleBar更加的复杂,因为对话框显示的位置还有底部显示,中心显示,顶部显示,以及动画等,因此。对于对话框,我们也可以封装以下。这里我们还是采用Builder模式来封装。

封装思路:将UI实现及事件和我们对话框基本属性进行解耦,从而一套对话框框架可以实现多种布局及UI实现。

2 BaseDialog架构图

封装的第一版的对话框架构图如下:



BaseDialog:

采用Builder模式来封装,将各种参数都封装到DialogParams这个类中

mParams 主要是对话框在屏幕切换方向后重建时使用的

BaseDialog.Builder:

提供对对话框的各种参数的设置,最后以Builder模式创建对话框。

DialogParams:

对话框参数类,主要包括对话框的宽高,布局方向,是否可取消,内容View以及监听事件等

DialogViewHolder:

辅助类,用于对对话框中的内容View进行简单的Text设置,图片设置,事件设置等

DialogListener:

事件监听器

这个版本目前还不是非常成熟,不过已经能引入项目使用了,后续还有优化的空间。

3 BaseDaialog的设计及实现

先来看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<Integer, Object>());
mBuilderParams.mEventMap.put(DRAWABLE,new HashMap<Integer, Object>());
mBuilderParams.mEventMap.put(LISTENER,new HashMap<Integer, Object>());
}

/**
* 设置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: 1273482124@qq.com
* @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<String,HashMap<Integer,Object>> 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: 1273482124@qq.com
* @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<WeakReference<View>> mViews;

public DialogViewHolder(BaseDialog dialog){
mViews = new SparseArray<>();
mDialog = dialog;
}

/**
* 根据id获取对应的view
* @param viewId
* @param <T>
* @return
*/
public <T extends View> T getView(int viewId){
WeakReference<View> 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>(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: 1273482124@qq.com
* @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<Integer,Object> hashMap = mParams.mEventMap.get(TEXT);
for (Map.Entry<Integer,Object> entry : hashMap.entrySet()){
TextView view = (TextView) mParams.mContentView.findViewById(entry.getKey());
view.setText((CharSequence) entry.getValue());
}

hashMap = mParams.mEventMap.get(DRAWABLE);
for (Map.Entry<Integer,Object> 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<Integer,Object> 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 <T>
* @return
*/
public <T extends View> 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目录

4 BaseDialog应用实例

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


效果如下:



后续再对该框架进行优化
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: