Android View 高级框架二 Builder模式打造通用对话框
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()); 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: 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> 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> 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: 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 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目录
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();
效果如下:
后续再对该框架进行优化
更多相关文章
- [android盈利模式探索]我也分享一下我Android的收入数据
- 在Android中分享内容到微信
- android webview设置内容的字体大小
- android内容提供者ContentProvider,UriMatcher和内容观察者Conte
- android菜单和对话框
- Android获取指定URL的内容
- [Android]取得Dialog中EditText的内容问题
- Android之博客案例 及 获取指定URL的网页内容