Builder設計模式構(gòu)建自定義萬能的Dialog

1. 概述


基于上節(jié)課我們對AlertDialog源碼的分析,那么這節(jié)課我們就一起來寫一個 萬能的 Dialog外恕。

2. 分析


其實主要就是三個類,分別是AlertController、AlertDialog(萬能的Dialog)、DialogViewHelper(Dialog的輔助處理類)转质,下邊就是這3個類的具體的實現(xiàn)方式
AlertController代碼如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/5 17:04
 * Version 1.0
 * Params:
 * Description:
*/
class AlertController {

    private AlertDialog mDialog;
    private Window mWindow;

    private DialogViewHelper mViewHelper;

    public AlertController(AlertDialog dialog, Window window) {
        this.mDialog = dialog;
        this.mWindow = window;
    }

    public void setViewHelper(DialogViewHelper viewHelper) {
        this.mViewHelper = viewHelper;
    }

    /**
     * 設置文本
     *
     * @param viewId
     * @param text
     */
    public void setText(int viewId, CharSequence text) {
        mViewHelper.setText(viewId, text);
    }

    public <T extends View> T getView(int viewId) {
        return mViewHelper.getView(viewId);
    }

    /**
     * 設置點擊事件
     *
     * @param viewId
     * @param listener
     */
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        mViewHelper.setOnclickListener(viewId, listener);
    }

    /**
     * 獲取Dialog
     *
     * @return
     */
    public AlertDialog getDialog() {
        return mDialog;
    }

    /**
     * 獲取Dialog的Window
     *
     * @return
     */
    public Window getWindow() {
        return mWindow;
    }

    public static class AlertParams {
        public Context mContext;
        public int mThemeResId;
        // 點擊空白是否能夠取消  默認點擊陰影可以取消
        public boolean mCancelable = true;
        // dialog Cancel監(jiān)聽
        public DialogInterface.OnCancelListener mOnCancelListener;
        // dialog Dismiss監(jiān)聽
        public DialogInterface.OnDismissListener mOnDismissListener;
        // dialog Key監(jiān)聽
        public DialogInterface.OnKeyListener mOnKeyListener;
        // 布局View
        public View mView;
        // 布局layout id
        public int mViewLayoutResId;
        // 存放字體的修改
        public SparseArray<CharSequence> mTextArray = new SparseArray<>();
        // 存放點擊事件
        public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();
        // 寬度
        public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
        // 動畫
        public int mAnimations = 0;
        // 位置
        public int mGravity = Gravity.CENTER;
        // 高度
        public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;

        public AlertParams(Context context, int themeResId) {
            this.mContext = context;
            this.mThemeResId = themeResId;
        }

        /**
         * 綁定和設置參數(shù)
         *
         * @param mAlert
         */
        public void apply(AlertController mAlert) {
            // 完善這個地方 設置參數(shù)

            // 1. 設置Dialog布局  DialogViewHelper
            DialogViewHelper viewHelper = null;
            if (mViewLayoutResId != 0) {
                viewHelper = new DialogViewHelper(mContext, mViewLayoutResId);
            }

            if (mView != null) {
                viewHelper = new DialogViewHelper();
                viewHelper.setContentView(mView);
            }

            if (viewHelper == null) {
                throw new IllegalArgumentException("請設置布局setContentView()");
            }

            // 給Dialog 設置布局
            mAlert.getDialog().setContentView(viewHelper.getContentView());

            // 設置 Controller的輔助類
            mAlert.setViewHelper(viewHelper);

            // 2.設置文本
            int textArraySize = mTextArray.size();
            for (int i = 0; i < textArraySize; i++) {
                mAlert.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
            }


            // 3.設置點擊
            int clickArraySize = mClickArray.size();
            for (int i = 0; i < clickArraySize; i++) {
                mAlert.setOnclickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
            }

            // 4.配置自定義的效果  全屏  從底部彈出    默認動畫
            Window window = mAlert.getWindow();
            // 設置位置
            window.setGravity(mGravity);

            // 設置動畫
            if (mAnimations != 0) {
                window.setWindowAnimations(mAnimations);
            }

            // 設置寬高
            WindowManager.LayoutParams params = window.getAttributes();
            params.width = mWidth;
            params.height = mHeight;
            window.setAttributes(params);
        }
    }
}

萬能的Dialog,即就是AlertDialog帖世,代碼如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/5 17:24
 * Version 1.0
 * Params:
 * Description:  自定義萬能的  Dialog
*/

public class AlertDialog extends Dialog {

    private AlertController mAlert;


    public AlertDialog(Context context, int themeResId) {
        super(context, themeResId);
        mAlert = new AlertController(this, getWindow());
    }

    /**
     * 設置文本
     *
     * @param viewId
     * @param text
     */
    public void setText(int viewId, CharSequence text) {
        mAlert.setText(viewId,text);
    }

    public <T extends View> T getView(int viewId) {
        return mAlert.getView(viewId);
    }

    /**
     * 設置點擊事件
     *
     * @param viewId
     * @param listener
     */
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        mAlert.setOnclickListener(viewId,listener);
    }


    public static class Builder {

        private final AlertController.AlertParams P;

        /**
         * 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(Context context) {
            this(context, R.style.dialog);
        }


        /**
         * 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 android.R.style#Theme_Material_Dialog}, to replace all
         * attributes in the parent {@code context}'s theme including primary
         * and accent colors.
         * <p/>
         * To preserve attributes such as primary and accent colors, the
         * {@code themeResId} may instead be specified as an overlay theme such
         * as {@link android.R.style#ThemeOverlay_Material_Dialog}. This will
         * override only the window attributes necessary to style the alert
         * window as a dialog.
         * <p/>
         * Alternatively, the {@code themeResId} may be specified as {@code 0}
         * to use the parent {@code context}'s resolved value for
         * {@link android.R.attr#alertDialogTheme}.
         *
         * @param context    the parent context
         * @param themeResId the resource ID of the theme against which to inflate
         *                   this dialog, or {@code 0} to use the parent
         *                   {@code context}'s default alert dialog theme
         */
        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(context, themeResId);
        }


        /**
         * Sets a custom view to be the contents of the alert dialog.
         * <p/>
         * When using a pre-Holo theme, if the supplied view is an instance of
         * a {@link ListView} then the light background will be used.
         * <p/>
         * <strong>Note:</strong> To ensure consistent styling, the custom view
         * should be inflated or constructed using the alert dialog's themed
         * context obtained via {@link #getContext()}.
         *
         * @param view the view to use as the contents of the alert dialog
         * @return this Builder object to allow for chaining of calls to set
         * methods
         */
        public Builder setContentView(View view) {
            P.mView = view;
            P.mViewLayoutResId = 0;
            return this;
        }

        // 設置布局內(nèi)容的layoutId
        public Builder setContentView(int layoutId) {
            P.mView = null;
            P.mViewLayoutResId = layoutId;
            return this;
        }

        // 設置文本
        public Builder setText(int viewId,CharSequence text){
            P.mTextArray.put(viewId,text);
            return this;
        }

        // 設置點擊事件
        public Builder setOnClickListener(int view , View.OnClickListener listener){
            P.mClickArray.put(view,listener);
            return this;
        }

        // 配置一些萬能的參數(shù)
        public Builder fullWidth(){
            P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
            return this;
        }

        /**
         * 從底部彈出
         * @param isAnimation 是否有動畫
         * @return
         */
        public Builder formBottom(boolean isAnimation){
            if(isAnimation){
                P.mAnimations = R.style.dialog_from_bottom_anim;
            }
            P.mGravity = Gravity.BOTTOM;
            return this;
        }


        /**
         * 設置Dialog的寬高
         * @param width
         * @param height
         * @return
         */
        public Builder setWidthAndHeight(int width, int height){
            P.mWidth = width;
            P.mHeight = height;
            return this;
        }

        /**
         * 添加默認動畫
         * @return
         */
        public Builder addDefaultAnimation(){
            P.mAnimations = R.style.dialog_scale_anim;
            return this;
        }

        /**
         * 設置動畫
         * @param styleAnimation
         * @return
         */
        public Builder setAnimations(int styleAnimation){
            P.mAnimations = styleAnimation;
            return this;
        }

        /**
         * Sets whether the dialog is cancelable or not.  Default is true.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setCancelable(boolean cancelable) {
            P.mCancelable = cancelable;
            return this;
        }

        /**
         * Sets the callback that will be called if the dialog is canceled.
         *
         * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than
         * being canceled or one of the supplied choices being selected.
         * If you are interested in listening for all cases where the dialog is dismissed
         * and not just when it is canceled, see
         * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener) setOnDismissListener}.</p>
         * @see #setCancelable(boolean)
         * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
            P.mOnCancelListener = onCancelListener;
            return this;
        }

        /**
         * Sets the callback that will be called when the dialog is dismissed for any reason.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
            P.mOnDismissListener = onDismissListener;
            return this;
        }

        /**
         * Sets the callback that will be called if a key is dispatched to the dialog.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
            P.mOnKeyListener = onKeyListener;
            return this;
        }


        /**
         * Creates an {@link AlertDialog} with the arguments supplied to this
         * builder.
         * <p/>
         * Calling this method does not display the dialog. If no additional
         * processing is needed, {@link #show()} may be called instead to both
         * create and display the dialog.
         */
        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }


        /**
         * Creates an {@link AlertDialog} with the arguments supplied to this
         * builder and immediately displays the dialog.
         * <p/>
         * Calling this method is functionally identical to:
         * <pre>
         *     AlertDialog dialog = builder.create();
         *     dialog.show();
         * </pre>
         */
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}

DialogViewHelper休蟹,是Dialog的一個輔助處理類,代碼如下:

class DialogViewHelper {

    private View mContentView = null;
    // 防止霸氣側(cè)漏
    private SparseArray<WeakReference<View>> mViews;

    public DialogViewHelper(Context context, int layoutResId) {
        this();
        mContentView = LayoutInflater.from(context).inflate(layoutResId, null);
    }


    public DialogViewHelper() {
        mViews = new SparseArray<>();
    }

    /**
     * 設置布局View
     *
     * @param contentView
     */
    public void setContentView(View contentView) {
        this.mContentView = contentView;
    }

    /**
     * 設置文本
     *
     * @param viewId
     * @param text
     */
    public void setText(int viewId, CharSequence text) {
        // 每次都 findViewById   減少findViewById的次數(shù)
        TextView tv = getView(viewId);
        if (tv != null) {
            tv.setText(text);
        }
    }

    public <T extends View> T getView(int viewId) {
        WeakReference<View> viewReference = mViews.get(viewId);
        // 側(cè)漏的問題  到時候到這個系統(tǒng)優(yōu)化的時候再去介紹
        View view = null;
        if (viewReference != null) {
            view = viewReference.get();
        }

        if (view == null) {
            view = mContentView.findViewById(viewId);
            if (view != null) {
                mViews.put(viewId, new WeakReference<>(view));
            }
        }
        return (T) view;
    }

    /**
     * 設置點擊事件
     *
     * @param viewId
     * @param listener
     */
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        View view = getView(viewId);
        if (view != null) {
            view.setOnClickListener(listener);
        }
    }

    /**
     * 獲取ContentView
     *
     * @return
     */
    public View getContentView() {
        return mContentView;
    }
}

3. 具體使用


萬能的 Dialog的具體使用如下:

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/5 18:05
 * Version 1.0
 * Params:
 * Description:  萬能Dialog 的具體使用場景
*/
public class MainActivity extends BaseSkinActivity implements View.OnClickListener{


    @ViewById(R.id.btn_dialog)
    Button btn_dialog ;


    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void initTitle() {

    }

    @Override
    protected void initView() {
        btn_dialog.setOnClickListener(this);
    }

    @Override
    protected void initData() {

    }


    @Override
    public void onClick(View v) {
        showDialogFromBottom() ;
    }

    private void showDialogFromBottom() {
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setContentView(R.layout.detail_comment_dialog)
                .formBottom(true).fullWidth().show();

        // 獲取EditText的輸入框的內(nèi)容
        final EditText commentEt = dialog.getView(R.id.comment_editor) ;
        dialog.setOnclickListener(R.id.submit_btn , new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this , "輸入的內(nèi)容為:" + commentEt.getText().toString() , Toast.LENGTH_SHORT).show();
            }
        });


        dialog.setOnclickListener(R.id.account_icon_weibo , new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this , "點擊微博分享了" + commentEt.getText().toString() , Toast.LENGTH_SHORT).show();
            }
        });


        dialog.setOnclickListener(R.id.account_icon_tencent, new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this , "點擊qq分享了" + commentEt.getText().toString() , Toast.LENGTH_SHORT).show();
            }
        });
    }

}

具體代碼已上傳至github:
https://github.com/shuai999/EssayJoke_day_05.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日矫,一起剝皮案震驚了整個濱河市赂弓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哪轿,老刑警劉巖盈魁,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窃诉,居然都是意外死亡杨耙,警方通過查閱死者的電腦和手機赤套,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來按脚,“玉大人于毙,你說我怎么就攤上這事「ò幔” “怎么了唯沮?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堪遂。 經(jīng)常有香客問我介蛉,道長,這世上最難降的妖魔是什么溶褪? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任币旧,我火速辦了婚禮,結(jié)果婚禮上猿妈,老公的妹妹穿的比我還像新娘吹菱。我一直安慰自己,他們只是感情好彭则,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布鳍刷。 她就那樣靜靜地躺著,像睡著了一般俯抖。 火紅的嫁衣襯著肌膚如雪输瓜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天芬萍,我揣著相機與錄音尤揣,去河邊找鬼。 笑死柬祠,一個胖子當著我的面吹牛北戏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓶盛,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼最欠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惩猫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚜点,失蹤者是張志新(化名)和其女友劉穎轧房,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绍绘,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡奶镶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年迟赃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厂镇。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纤壁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捺信,到底是詐尸還是另有隱情酌媒,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布迄靠,位于F島的核電站秒咨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏掌挚。R本人自食惡果不足惜雨席,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吠式。 院中可真熱鬧陡厘,春花似錦、人聲如沸特占。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摩钙。三九已至罢低,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胖笛,已是汗流浹背网持。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留长踊,地道東北人功舀。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像身弊,于是被迫代替她去往敵國和親辟汰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354