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

1 前言

在我们的日常开发中,对话框是一个常见的组件,例如下面的对话框,分别是三种不同类型的对话框
Android View 高级框架二 Builder模式打造通用对话框_第1张图片Android View 高级框架二 Builder模式打造通用对话框_第2张图片
Android View 高级框架二 Builder模式打造通用对话框_第3张图片

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

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

2 BaseDialog架构图

封装的第一版的对话框架构图如下:
Android View 高级框架二 Builder模式打造通用对话框_第4张图片
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: [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目录

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,UI开发)