如何從0構(gòu)造出一個(gè)萬能的Dialog

前言

  • 第一次在簡書發(fā)表文章是目,突然就有點(diǎn)小緊張,寫不好标捺,會不會有人打我啊懊纳。
  • 該片文章所講的Dialog適用于那種漂亮可愛妹紙?jiān)O(shè)計(jì)的多種彈框,各種彈亡容,各種動畫嗤疯,各種方位、姿勢和變化闺兢。
  • 寫該文章的初衷在于剛好我們公司就有這么一對可愛的設(shè)計(jì)師茂缚,這樣多樣式的彈框,如果一個(gè)個(gè)單獨(dú)寫會造成代碼臃腫和可讀性十分差屋谭,所以做了一下二次封裝脚囊。
  • 本文章其實(shí)早在兩個(gè)月前就打算寫了,由于項(xiàng)目時(shí)間十分緊張(通宵加班加得我懷疑猿生)桐磁,所以拖到了現(xiàn)在悔耘,終于開始寫了(我在調(diào)休,嘿嘿嘿...)。

本文會用到Builder設(shè)計(jì)模式:Android開發(fā)---Builder 模式必知必會

  • 給大家獻(xiàn)上效果圖我擂,彈框自行yy出來的衬以,可能有點(diǎn)丑,不過這不是重點(diǎn)校摩,因?yàn)槌鰣D是設(shè)計(jì)妹紙的事情看峻。
效果一.png
效果二.png
效果三.png
效果四.png
效果五.png
效果六.png

以上是部分效果展示,下面我們將進(jìn)入正題:

目錄

1.繼承Dialog衙吩,高仿一個(gè)AlertDialog

1.1 開始按照v7包的AlertDialog擼一個(gè)輪子
  • 閱讀并理解AlertDialog源碼

上一個(gè)鏈接讓大家看看如何簡單自定義:Android AlertDialog/AlertDialog.builder 以及 自定義AlertDialog方法

  • 在package android.support.v7.app下面我們找到這個(gè)類AlertDialog.class 我精簡了一些 剩下的主要內(nèi)容如下:
private final AlertController mAlert;
static final int LAYOUT_HINT_NONE = 0;
static final int LAYOUT_HINT_SIDE = 1;

protected AlertDialog(@NonNull Context context) {
    this(context, 0);
}


protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
    super(context, resolveDialogTheme(context, themeResId));
    mAlert = new AlertController(getContext(), this, getWindow());
}

protected AlertDialog(@NonNull Context context, boolean cancelable,
        @Nullable OnCancelListener cancelListener) {
    this(context, 0);
    setCancelable(cancelable);
    setOnCancelListener(cancelListener);
}

private static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {
    if (resid >= 0x01000000) {   // start of real resource IDs.
        return resid;
    } else {
        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
        return outValue.resourceId;
    }
}


public void setView(View view) {
    mAlert.setView(view);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAlert.installContent();
}


public static class Builder {
    private final AlertController.AlertParams P;
    private final int mTheme;

  
    public Builder(@NonNull Context context) {
        this(context, resolveDialogTheme(context, 0));
    }

    public Builder(@NonNull Context context, @StyleRes int themeResId) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
                context, resolveDialogTheme(context, themeResId)));
        mTheme = themeResId;
    }

  
    @NonNull
    public Context getContext() {
        return P.mContext;
    }


    public Builder setCancelable(boolean cancelable) {
        P.mCancelable = cancelable;
        return this;
    }

  
    public Builder setOnCancelListener(OnCancelListener onCancelListener) {
        P.mOnCancelListener = onCancelListener;
        return this;
    }


    public Builder setOnDismissListener(OnDismissListener onDismissListener) {
        P.mOnDismissListener = onDismissListener;
        return this;
    }

  
    public Builder setOnKeyListener(OnKeyListener onKeyListener) {
        P.mOnKeyListener = onKeyListener;
        return this;
    }

   
    public Builder setView(int layoutResId) {
        P.mView = null;
        P.mViewLayoutResId = layoutResId;
        P.mViewSpacingSpecified = false;
        return this;
    }

   
    public Builder setView(View view) {
        P.mView = view;
        P.mViewLayoutResId = 0;
        P.mViewSpacingSpecified = false;
        return this;
    }

    @Deprecated
    public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
            int viewSpacingRight, int viewSpacingBottom) {
        P.mView = view;
        P.mViewLayoutResId = 0;
        P.mViewSpacingSpecified = true;
        P.mViewSpacingLeft = viewSpacingLeft;
        P.mViewSpacingTop = viewSpacingTop;
        P.mViewSpacingRight = viewSpacingRight;
        P.mViewSpacingBottom = viewSpacingBottom;
        return this;
    }

    public AlertDialog create() {
        final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
        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;
    }

    public AlertDialog show() {
        final AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }
}

從源碼可以看出互妓,類中有個(gè)AlertController類型的屬性,我們進(jìn)入這個(gè)類文件可以看到定義了很多屬性,比如Window View 上下左右邊距等等车猬,這個(gè)類的作用我們將會在2.1中提到霉猛。

  • 開始自定義PowerfulDialog

我們新建一個(gè)PowerfulDialog仿照這個(gè)類,去繼承Dialog珠闰,這里有人拿著刀就要說了惜浅,人家是繼承的AppCompatDialog(這里我解釋一下AppCompatDialog ,點(diǎn)進(jìn)去看源碼伏嗜,發(fā)現(xiàn)它還是繼承的Dialog坛悉,是為了簡便的實(shí)現(xiàn)Material Design風(fēng)格的彈框),所以很多人使用AlertDialog承绸,基本都會是長這樣:

md風(fēng)格彈框.png

當(dāng)然了裸影,原生的AlertDialog可以通過Biulder中的setView方法去設(shè)置自定義的View但是,這樣寫達(dá)不到我們理想中的代碼量盡可能少的要求军熏,好了轩猩,現(xiàn)在我們直接繼承Dialog,不熟悉Builder的建議可以直接copy一份源碼荡澎,直接在源碼上面修改均践。

我們會在PowerFulDialog中寫一個(gè)Builder的靜態(tài)內(nèi)部類,這個(gè)靜態(tài)類主要是用來通知控制器來設(shè)置這個(gè)彈框的事件摩幔、動畫彤委、屬性及view的操作,所以我們一開始要擴(kuò)展Dialog的功能或衡,就可以在這里面配置編寫相應(yīng)的方法焦影,在Builder內(nèi)部的方法一般是可以鏈?zhǔn)讲僮鞯模_(dá)到靈活配置的作用封断。

在Builder靜態(tài)內(nèi)部類外部的方法除了構(gòu)造函數(shù)以外斯辰,其他的方法應(yīng)該是可以直接通過自定義的Dialog獲取到的屬性或事件等,這些操作大多是單個(gè)操作澄港,主要是獲取某些屬性或者事件椒涯,比如獲取Dialog中View的某個(gè)控件等。

好了 回梧,PowerFulDialog這個(gè)類的組成大概就是這個(gè)樣子废岂。下面我們來說說直接和彈框打交道的控制類PowerfulController.class


2.編寫一個(gè)控制類(統(tǒng)一配置事件、參數(shù)和彈窗屬性)

2.1 為什么要編寫一個(gè)控制類
  • 增強(qiáng)代碼可讀性狱意,邏輯清晰湖苞,該類主要是設(shè)置彈框?qū)傩院屯ㄟ^輔助類設(shè)置彈框內(nèi)容。而PowerFulDialog類主要是負(fù)責(zé)去操作Dialog的狀態(tài)(create show dismiss...)

  • 方便后期需求擴(kuò)展详囤,增加屬性時(shí)候從一定程度上減少代碼間的耦合度

2.2 如何編寫這個(gè)控制類
  • 首先我們可以參考一下源碼财骨,在這個(gè)類里面我們會寫一些必要的參數(shù)镐作,被傳入的當(dāng)前需要顯示的Dialog、當(dāng)前Dialog需展示的Window隆箩、還有一個(gè)就是我們?yōu)榱藢iT處理View而新建的一個(gè)ViewHelper輔助類该贾,并且會寫一些設(shè)置view相關(guān)的方法給AlertParams內(nèi)部類調(diào)用。

  • 其次捌臊,看AlertController源碼我們也可以發(fā)現(xiàn)杨蛋,我們會在PowerfulController這個(gè)類里面建立一個(gè)靜態(tài)內(nèi)部類AlertParams,作用主要是用來存放需要配置的參數(shù)屬性(顯示的view理澎、上下文逞力、文字、顏色糠爬、大小寇荧、位置等),一個(gè)Dialog能夠顯示的基本屬性條件基本上都必須寫在這個(gè)內(nèi)部類里面执隧。

  • 在內(nèi)部類方法apply中揩抡,我們會傳當(dāng)前Dialog的Controller 對象,然后去設(shè)置這個(gè)傳進(jìn)來的Controller對象镀琉,并獲取到當(dāng)前處理view的輔助類捅膘,并完成view的處理。

  • 那么滚粟,什么時(shí)候應(yīng)該調(diào)用這個(gè)apply方法呢,上面說過了刃泌,apply方法是去給這個(gè)Dialog的Controller(控制器)下達(dá)配置Dialog(包括設(shè)置view凡壤、大小、字體耙替、位置等)的指令亚侠,所以,這個(gè)方法應(yīng)該是我們在Dialog執(zhí)行create方法的時(shí)候調(diào)用俗扇。鏈?zhǔn)脚渲猛瓿珊笳{(diào)用create方法硝烂,我們就可以得到我們配置后建造出來的Dialog了。

  • 有的人就有疑問了铜幽,鏈?zhǔn)奖磉_(dá)后有的直接用show也可以配置啊滞谢。如果真的有這樣的人,我勸你還是多看看源碼除抛,再出來裝逼狮杨,其實(shí)它也還是走了這一步的,如下圖到忽。

show之前也執(zhí)行了create.png

3.編寫一個(gè)View輔助類(用于實(shí)際直接操作view)

2.1 為什么要編寫一個(gè)輔助類
  • 增強(qiáng)代碼可讀性橄教,邏輯清晰,該類主要是獲取到指定view并直接設(shè)置View的屬性,而PowerfulController主要是設(shè)置整個(gè)window的狀態(tài)护蝶、動畫华烟、位置等,還有操控輔助類設(shè)置view

  • 方便后期需求擴(kuò)展持灰,增加屬性時(shí)候從一定程度上減少代碼間的耦合度

2.2 如何編寫一個(gè)輔助類
  • 既然該類的主要作用是用于設(shè)置view的屬性盔夜,那么必不可少的參數(shù)有Context、View(或者布局ID),所以構(gòu)造函數(shù)需要傳遞這兩個(gè)參數(shù)搅方,這個(gè)類內(nèi)部應(yīng)該提供設(shè)置view的方法比吭。DialogViewHelper.class代碼如下:
private View mDialogView=null;
//防止泄露
private SparseArray<WeakReference<View>> mViews;

public DialogViewHelper(Context mContext, int mViewLayoutResId) {
    this();
    mDialogView= LayoutInflater.from(mContext).inflate(mViewLayoutResId,null);
}

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

/**
 * 設(shè)置布局
 * @param mView
 * */
public void setDialogView(View mView) {
    this.mDialogView=mView;
}

/**
 * 設(shè)置文本
 * @param viewId
 * @param text
 */
public void setText(int viewId, CharSequence text) {

    TextView tv=getView(viewId);
    if(tv!=null){
        tv.setText(text==null?"":text);
    }

}

/**
 * 設(shè)置文本顏色
 * @param viewId
 * @param colorRes
 */
public void setTextColor(Context context, int viewId, int colorRes) {

    TextView tv=getView(viewId);
    if(tv!=null){
        tv.setTextColor(context.getResources().getColor(colorRes==0? R.color.text_gray_normal:colorRes));
    }

}

/**
 * 設(shè)置圖片
 * @param viewId
 * @param imgRes
 */
public void setImage(int viewId, int imgRes) {

    ImageView iv=getView(viewId);
    if(iv!=null){
        iv.setImageResource(imgRes==0? R.mipmap.default_head:imgRes);
    }

}
/**
 * 設(shè)置view的顯示和隱藏
 * @param viewId
 * @param visibilityMode
 */
public void setVisiable(int viewId, int visibilityMode) {
    View v=getView(viewId);
    if(v!=null){
        v.setVisibility(visibilityMode);
    }
}
/**
 * 優(yōu)化findViewById
 * @param viewId
 * @param <T>
 * @return
 */
public  <T extends View> T getView(int viewId) {
    View v=null;
    WeakReference<View> viewRefrence=mViews.get(viewId);
    if(viewRefrence!=null){
        v=viewRefrence.get();
    }
    if(v==null){
        v=mDialogView.findViewById(viewId);
        if(v!=null){
            mViews.put(viewId,new WeakReference<View>(v));
        }
    }
    return (T)v;
}

/**
 * 設(shè)置點(diǎn)擊事件
 * @param viewId
 * @param onClickListener
 */
public void setOnclickListener(int viewId, View.OnClickListener onClickListener) {
    View v=getView(viewId);
    if(v!=null){
        v.setOnClickListener(onClickListener);
    }

}

public View getDialogView() {
    return mDialogView;
}

總結(jié)

1.做一個(gè)“懶”的程序員
  • 這里的懶,不是指整天喜歡裝逼姨涡,不干事兒衩藤,不敲代碼,不在實(shí)踐中求真知的那個(gè)懶涛漂,指的是赏表,會用簡短高效的代碼,去完成項(xiàng)目中的需求匈仗。
  • “用最少的代碼實(shí)現(xiàn)最牛逼的效果”相信這是每個(gè)“懶”程序員的終極目標(biāo)瓢剿,所以,我們平時(shí)在項(xiàng)目實(shí)際開發(fā)中悠轩,多去從中思考间狂,有沒有更加優(yōu)秀的實(shí)現(xiàn)方式,多嘗試火架,多積累鉴象。
2.時(shí)刻想著這段代碼是否還可以被優(yōu)化
  • 在這個(gè)demo編寫過程中,大家可以看到何鸡,有很多地方用到了SparseArray和弱引用纺弊。
  • 其中我們主要是用SparseArray這個(gè)集合去存儲已經(jīng)從Dialog中取到的view id和對應(yīng)的值,剛好利用view id為int類型骡男,使用SparseArray更加節(jié)約內(nèi)存淆游,從而提高性能,當(dāng)某個(gè)擁有id的view通過findviewbyid被查詢到后都會放入集合隔盛,下次使用時(shí)不會再經(jīng)過findviewbyid而是直接從SparseArray去取出犹菱,如若沒有查詢到該id則又通過findviewbyid查詢,這樣就提高了性能骚亿,減少了內(nèi)存開支已亥。
  • 使用弱引用主要是為了系統(tǒng)內(nèi)存不足時(shí)候造成內(nèi)存泄漏的問題。

老司機(jī)帶你理解SparseArray:Android內(nèi)存優(yōu)化(使用SparseArray和ArrayMap代替HashMap)
老司機(jī)帶你理解為什么要使用弱引用:Java 理論與實(shí)踐
用弱引用堵住內(nèi)存泄漏)

3.要善于去學(xué)習(xí)源碼
  • 講真来屠,本文demo其實(shí)就是一個(gè)源碼擴(kuò)展和優(yōu)化版本虑椎,其實(shí)其中的思維有百分之七八十的相似震鹉,無論是設(shè)計(jì)模式還是實(shí)現(xiàn)思路。所以捆姜,多看源碼传趾,你會學(xué)到很多東西的。

在線查看Android源碼地址:在線查看android源碼

4.最后奉上我的demo
  • 最近上傳到的GitHub泥技,未來會長期更新和完善浆兰,如果有什么好的建議或者問題,歡迎提issue珊豹。
  • 在簡書的第一篇文章簸呈,如果寫得有不好的地方或有誤的地方,還希望指正店茶,在下感激不盡蜕便。
  • 最后,歡迎大家star and fork贩幻。

一個(gè)萬能的Dialog demo地址:PowerfulDialog轿腺,為程序猿而生


請點(diǎn)贊,因?yàn)槟墓膭?lì)將是我寫作的最大動力丛楚!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末族壳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趣些,更是在濱河造成了極大的恐慌仿荆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坏平,死亡現(xiàn)場離奇詭異赖歌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)功茴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孽亲,“玉大人坎穿,你說我怎么就攤上這事》稻ⅲ” “怎么了玲昧?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長篮绿。 經(jīng)常有香客問我孵延,道長,這世上最難降的妖魔是什么亲配? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任尘应,我火速辦了婚禮惶凝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘犬钢。我一直安慰自己苍鲜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布玷犹。 她就那樣靜靜地躺著混滔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歹颓。 梳的紋絲不亂的頭發(fā)上坯屿,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音巍扛,去河邊找鬼领跛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛电湘,可吹牛的內(nèi)容都是我干的隔节。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寂呛,長吁一口氣:“原來是場噩夢啊……” “哼怎诫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贷痪,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤幻妓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后劫拢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肉津,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年舱沧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妹沙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熟吏,死狀恐怖距糖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牵寺,我是刑警寧澤悍引,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站帽氓,受9級特大地震影響趣斤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜黎休,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一浓领、第九天 我趴在偏房一處隱蔽的房頂上張望玉凯。 院中可真熱鬧,春花似錦镊逝、人聲如沸壮啊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歹啼。三九已至,卻和暖如春座菠,著一層夾襖步出監(jiān)牢的瞬間狸眼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工浴滴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拓萌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓升略,卻偏偏與公主長得像微王,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子品嚣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容