(轉)Android 自定義Dialog實現步驟及封裝

發條魚發表於2018-09-21

作者:青蛙要fly
連結:https://www.jianshu.com/p/64446940eccf
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

在專案中,我們會遇到各種各樣的介面需求,比如對話方塊和選擇框,都是會配合具體專案的UI介面來做,而不是說用自帶的彈出框。比如下面在登入介面的二個對話方塊效果。都是我在做具體專案中所要求實現的:

1.輸入有誤時彈出的對話方塊

2.選擇角色登入時的對話方塊

這裡倒不是說自定義Dialog的教程,因為自定義Dialog大家基本都會。只是我在登入介面寫了這二個Dialog之後,我就覺得好煩,然後決定封裝了一個類,因為後面不同介面還有很多不同的彈框。為後期節省時間。倒不是說我這個封裝類寫的有多好,只是寫出來,大家可以看下,然後哪裡不好可以跟我提下意見。

讓我們一步步來看是如何自定這個自定義對話方塊及如何來進行封裝自己的自定義Dialog工具類。我就按照實際專案中,我的開發步驟來說明。

如何生成這種自定義對話方塊

實際開發中,我看到了第一個效果圖中的對話方塊,於是我馬上大手一揮,自定義了一個類ErrorDialog,繼承了Dialog。

public class ErrorDialog extends Dialog{
    public ErrorDialog(Context context) {
        super(context);
    }

    public ErrorDialog(Context context, int themeResId) {
        super(context, themeResId);
    }

    protected ErrorDialog(Context context, boolean cancelable
        , OnCancelListener cancelListener) {
        super(context, cancelable, cancelListener);
    }
}

我們可以看到有三個建構函式,這三個建構函式不是一定要都實現,但至少要實現一個建構函式。我們來具體說明下各個建構函式的作用。

  1. ErrorDialog(Context context):
    單純傳入Context,用的比較多。在程式碼中通過new ErrorDialog(context);來獲得Dialog的例項,然後使用show()方法進行展現。

  2. ErrorDialog(Context context, int themeResId)
    大家可以看到比第一個建構函式多了一個themeResId,就是我們可以傳入一個主題值,比如R.style.XXX。然後建構函式中會呼叫super(context, themeResId);等會生成的Dialog就會帶有這個R.style.XXX所設定的效果。

  3. ErrorDialog(Context context, boolean cancelable, OnCancelListener cancelListener):
    大家都知道,對話方塊彈出來後,預設情況下,我們在螢幕上觸控對話方塊以外的螢幕的介面,對話方塊會預設消失。我們平時做對話方塊的時候一般都是讓這個對話方塊點選外面的其他介面地方的時候不讓對話方塊消失,我們一般在程式碼中會這麼寫:setCanceledOnTouchOutside(false);。為什麼我提這個,沒錯,這個建構函式裡面的那個boolean cancelable控制的就是這個功能,<1>當傳入為true的時候,就是可以點選外面來讓對話方塊消失,然後消失的時候會呼叫後面第三個引數的cancelListener這個listener裡面的方法。我們可以在裡面做相應的監聽事件。<2>當傳入false。那麼點選外面區域,這個對話方塊也就不會消失,而且後面的那個listener也不會被呼叫。

好了,建構函式說好後。我們來具體看如何生成介面Dialog介面。於是我大手再次一揮。寫了個對話方塊所需要的效果的Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="250dp"
    android:background="@drawable/background_loginerror"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:layout_marginTop="30dp"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/gong"
        />

    <TextView
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="您輸入的內容有誤"
        />

    <Button
        android:id="@+id/btn_cancel"
        android:layout_marginTop="20dp"
        android:layout_width="120dp"
        android:layout_height="40dp"
        android:text="@string/confirm"
        android:background="@drawable/background_loginbtn"
        />
</LinearLayout>

效果如下圖所示(那個感嘆號的圖片我這邊因為切圖沒有的問題。臨時換成了工商的圖示。反正不影響我們開發教程):

然後我們在自定義的ErrorDialog中寫oncreate方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    View view = View.inflate(context,R.layout.dialog_loginerror,null);
    setContentView(view);
}

然後這時候我以為就跟繼承Activity一樣。變成了我自定義的佈局介面。然後我滿心歡喜的在Activity中呼叫了:
ErrorDialog dialog = new ErrorDialog(this); dialog.show();

WTF!!
這是逗我嗎,我的自定義佈局明明是個圓角啊。怎麼變成了個長方形。
所以我就把我們自定義佈局的背景色換成其黑色。看下效果:

這下首先知道了。我們其實自定義的layout類似於是蓋在了底部白色的背景上面,恰好我們的自定義佈局也是白色。所以我們現在首先要把底部的那個白色背景變為透明,那樣,就會出現我們自定義佈局的圓角了

那我們下一步的目的就是要設定Dialog自定義的theme。

把Dialog自帶的白色背景色改為透明即可,很簡單。百度一搜一大把。哈哈。其實說到底就是繼承android:style/Theme.Dialog主題,然後再覆寫其中的幾個相關屬性,比如背景設定為透明,去除自帶的title等屬性。

(我在網上看的時候,網上有人推薦Android4.0後,不要使用Theme.Dialog。改為使用Theme.Holo.DialogWhenLarge。
Android4: 請放棄使用Theme.Dialog
當然對我們這個自定義佈局需求,繼承哪個都能實現效果。就看大家怎麼選擇了。
)

好,那我們就自定義繼承Theme.Dialog:

<style name="Dialog" parent="android:style/Theme.Dialog">
    <item name="android:background">@android:color/transparent</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
</style>

(如果有其他需求,再覆寫其他的屬性即可,本例中就只需要改這三個)

好了。然後在上面我們介紹過的生成Dialog例項中的第二個建構函式,是傳入Theme。我們呼叫:

ErrorDialog dialog = new ErrorDialog(this,R.style.Dialog);
dialog.show();

然後我們看到效果了:

 

 

起碼形狀變成我們的自定義佈局的形狀了。哈哈。但是這個Dialog大小和我們的自定義佈局大小不同。

下一步要處理Dialog呈現的自定義佈局的大小

還是老樣子,百度一搜一大把,好吧。我實在是太懶了。(不服氣我懶的話,過來打我哈。O(∩_∩)O)

Window win = getWindow();
WindowManager.LayoutParams lp = win.getAttributes();
lp.gravity = Gravity.CENTER;
lp.height = DensityUtil.dip2px(context,250);
lp.width = DensityUtil.dip2px(context,200);
win.setAttributes(lp);

我來解釋下,因為上面我們自定義佈局的大小就是

 android:layout_width="200dp"
 android:layout_height="250dp"

所以我們這裡也設定這個對話方塊的大小也設定為相同大小,這樣就等於顯示出我們自定義佈局大小。這裡因為高和寬改為我們自己自定義佈局大小,所以lp.gravity = Gravity.CENTER;這句也可以不寫,因為反正是正好完全填充。


所以我們當前的自定義Dialog程式碼變為:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    View view = View.inflate(context,R.layout.dialog_loginerror,null);
    setContentView(view);

    Window win = getWindow();
    WindowManager.LayoutParams lp = win.getAttributes();
    lp.height = DensityUtil.dip2px(context,250);
    lp.width = DensityUtil.dip2px(context,200);
    win.setAttributes(lp);
}

點選事件

  1. 一般我們專案中跳出了對話方塊,點選對話方塊外面的區域,是不能預設讓對話方塊消失的。所以我們需要新增setCanceledOnTouchOutside(false);
  2. 自定義佈局上面的按鈕點選事件的新增很簡單,因為上面已經拿到了自定義佈局的view的物件。比如我們上面的自定義佈局有個<確定>按鈕,我們點選按鈕讓對話方塊消失。我們只需要:
view.findViewById(R.id.btn_cancel).setOnClickListener(new Button.OnClickListener() {
    @Override
    public void onClick(View view) {
        dismiss();
    }
});

最終的自定義ErrorDialog程式碼:

public class ErrorDialog extends Dialog {
    public Context context;

    public ErrorDialog(Context context) {
        super(context);
        this.context = context;
    }

    public ErrorDialog(Context context, int theme) {
        super(context, theme);
        this.context = context;
    }

    public ErrorDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
        super(context, cancelable, cancelListener);
        this.context = context;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View view = View.inflate(context, R.layout.dialog_loginerror, null);
        setContentView(view);

        setCanceledOnTouchOutside(false);

        Window win = getWindow();
        WindowManager.LayoutParams lp = win.getAttributes();
        lp.height = DensityUtil.dip2px(context, 250);
        lp.width = DensityUtil.dip2px(context, 200);
        win.setAttributes(lp);

        view.findViewById(R.id.btn_cancel).setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });
    }
}

最後生成的介面如下:


----------------------------------------本文正文,封裝君正式登場-----------------------------------------------------

上面只是介紹了自定義Dialog的基本知識。而且這個ErrorDialog只能用於第一個效果圖所需的Dialog需求,然後比如我們要第二個效果圖的需求。然後自己再寫一個ChangeDialog???一個專案有5種不同的介面Dialog。我要寫5個自定義Dialog類???答案當然是NO,NO,NO。

我們來看下,上面我們完成ErrorDialog的時候,到底需要哪些東西,才能最後完成一個自定義Dialog。

  1. Context
  2. R.style.XXXX
  3. R.layout.XXXX
  4. setCanceledOnTouchOutside(XXXX);是否允許點選外部區域來讓Dialog消失
  5. WindowManager.LayoutParams物件類的height和width。
  6. 自定義佈局上各個View的點選事件

基本是上述五個需求。(額外需求,大家就在這基礎上封裝好的類中新增自己的需求即可)

我們也是模仿Dialog建立的Builder模式,自己寫個封裝類。
(Builder模式的介紹和用Android Studio外掛來快速自動生成程式碼,大家可以來看下我已經寫得文章:經典Builder/變種Builder模式及自動化生成程式碼外掛

我先上程式碼再來進行檢視:

public class CustomDialog extends Dialog {
    private Context context;
    private int height, width;
    private boolean cancelTouchout;
    private View view;

    private CustomDialog(Builder builder) {
        super(builder.context);
        context = builder.context;
        height = builder.height;
        width = builder.width;
        cancelTouchout = builder.cancelTouchout;
        view = builder.view;
    }


    private CustomDialog(Builder builder, int resStyle) {
        super(builder.context, resStyle);
        context = builder.context;
        height = builder.height;
        width = builder.width;
        cancelTouchout = builder.cancelTouchout;
        view = builder.view;
    }

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

        setContentView(view);

        setCanceledOnTouchOutside(cancelTouchout);

        Window win = getWindow();
        WindowManager.LayoutParams lp = win.getAttributes();
        lp.gravity = Gravity.CENTER;
        lp.height = height;
        lp.width = width;
        win.setAttributes(lp);
    }

    public static final class Builder {

        private Context context;
        private int height, width;
        private boolean cancelTouchout;
        private View view;
        private int resStyle = -1;


        public Builder(Context context) {
            this.context = context;
        }

        public Builder view(int resView) {
            view = LayoutInflater.from(context).inflate(resView, null);
            return this;
        }

        public Builder heightpx(int val) {
            height = val;
            return this;
        }

        public Builder widthpx(int val) {
            width = val;
            return this;
        }

        public Builder heightdp(int val) {
            height = DensityUtil.dip2px(context, val);
            return this;
        }

        public Builder widthdp(int val) {
            width = DensityUtil.dip2px(context, val);
            return this;
        }

        public Builder heightDimenRes(int dimenRes) {
            height = context.getResources().getDimensionPixelOffset(dimenRes);
            return this;
        }

        public Builder widthDimenRes(int dimenRes) {
            width = context.getResources().getDimensionPixelOffset(dimenRes);
            return this;
        }

        public Builder style(int resStyle) {
            this.resStyle = resStyle;
            return this;
        }

        public Builder cancelTouchout(boolean val) {
            cancelTouchout = val;
            return this;
        }

        public Builder addViewOnclick(int viewRes,View.OnClickListener listener){
            view.findViewById(viewRes).setOnClickListener(listener);
            return this;
        }


        public CustomDialog build() {
            if (resStyle != -1) {
                return new CustomDialog(this, resStyle);
            } else {
                return new CustomDialog(this);
            }
        }
    }
}


我這邊的Builder中對height和width寫了三種方式,比如直接寫入px的值就呼叫heightpx(),如果直接寫入dp值,就呼叫heightdp()。不過最多的應該還是呼叫heightDimenRes()方法。因為一般我們在寫自定義layout佈局的時候,height和width的數值肯定是去dimen.xml中獲取。所以我們在程式碼中生成這個自定義對話方塊的時候,也就直接呼叫了heightDimenRes(R.dimen.XXX)。這樣。我們什麼時候需求變了,說這個對話方塊的大小要進行更改,我們不需要更改程式碼,只需要在demen.xml中將數值修改即可。

然後我們再來寫上面的ErrorDialog:

CustomDialog.Builder builder = new CustomDialog.Builder(this);
dialog =
    builder.cancelTouchout(false)
            .view(R.layout.dialog_loginerror)
            .heightDimenRes(R.dimen.dialog_loginerror_height)
            .widthDimenRes(R.dimen.dialog_loginerror_width)
            .style(R.style.Dialog)
            .addViewOnclick(R.id.btn_cancel,new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    dialog.dismiss();
                }
            })
            .build();
dialog.show();

真是簡單!!!!!

好了我們現在要第二個效果圖的對話方塊了。比如我現在為了簡單。就做了個簡單的自定義Layout。

然後點選“經辦人”,“審批人”,“確認”按鈕,有不同點選效果。
生成這個對話方塊程式碼如下:

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.jbperson :
                Toast.makeText(aty, "選擇經辦人按鈕", Toast.LENGTH_SHORT).show();
                break;
            case R.id.spperson:
                Toast.makeText(aty, "選擇審批人按鈕", Toast.LENGTH_SHORT).show();
                break;
            case R.id.confirmbtn:
                Toast.makeText(aty, "點選確定按鈕", Toast.LENGTH_SHORT).show();
                 break;
        }
    }
};

CustomDialog.Builder builder = new CustomDialog.Builder(this);
CustomDialog dialog = builder
        .style(R.style.Dialog)
        .heightDimenRes(R.dimen.dilog_identitychange_height)
        .widthDimenRes(R.dimen.dilog_identitychange_width)
        .cancelTouchout(false)
        .view(R.layout.dialog_identitychange)
        .addViewOnclick(R.id.jbperson,listener)
        .addViewOnclick(R.id.spperson,listener)
        .addViewOnclick(R.id.confirmbtn,listener)
        .build();
        
dialog.show();

我們用手機檢視效果:

 

相關文章