天天看點

自定義dialog的方式,以及需要注意事項

上一次總結記錄了dialog的載體是什麼(window),在某種情況下setContentView會重置LayoutParams導緻我們之前設定好的LayoutParams無效,為什麼dialog依賴于activity;

這次總結一下自定義dialog的一些方式,以及需要注意的一些事項,好吧下面就來記錄一下自定義dialog的方式:

方式一:

public static AlertDialog showAddProductDialog(Context context, View.OnClickListener clickListener) {
        final AlertDialog dialog = new AlertDialog.Builder(context).setCancelable(false).create();
        dialog.show();
        Window window = dialog.getWindow();
        window.setContentView(R.layout.dialog_price_add);
        window.findViewById(R.id.dialog_price_cancel).setOnClickListener(clickListener);
        window.findViewById(R.id.dialog_price_confirm).setOnClickListener(clickListener);
        return dialog;
    }
           

上面這種方式類自動dialog,個人覺得是最簡單不過的了,也是用的比較多的方式。好了這種方式簡單歸簡單其實也是有需要我們注意的地方的。

比如在上一次dialog的總結中說到的,在show()之前設定LayoutParams,在show()後setContentView()就會重置了原來設定的LayoutParams;那麼肯能有人會提出質疑,為什麼不可以在show()之前去setContentView呢?下面就是要說說為什麼不在show()之前去setContentView,其實是因為會抛出

throw new AndroidRuntimeException(“requestFeature() must be called before adding content”);這個異常,是以不可以在show()之前調用,那到底為什麼呢?看下面的源碼:

@Override
    public void setContentView(int layoutResID) {
       ...
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...
    }
           

當這mContentParent == null就去調用installDecor();接着看:

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }
           

由上面可以看出當mDecor == null建立mDecor這裡不是我們的重點濾過,接着看當mContentParent為空的時候調用generateLayout(mDecor);建立mContentParent,好了setContentView介紹到此為止,畢竟後面的個人覺得都不重要;

這個時候問題就來了,就上面的那部分源碼還不足以說明在show()之前setContentView會抛出異常的問題,這個時候我們回歸到setContentView執行完以後調用的show()方法,從上一次總結提到過onCreate()方法是在show()裡面調用的,我們就去看看AlertDialog的onCreate方法,如下:

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

下面接着看mAlert.installContent();代碼如下:

public void installContent() {
        /* We use a custom title so never request a window title */
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
        setupDecor();
    }
           

這裡我們就看第一句mWindow.requestFeature(Window.FEATURE_NO_TITLE);其實異常就是從這裡面抛出來的,看看裡面的代碼:

@Override
    public boolean requestFeature(int featureId) {
        if (mContentParent != null) {
            throw new AndroidRuntimeException("requestFeature() must be called before adding content");
        }
        ...
    }
           

到這裡也就明朗了,當我們調用setContentView以後mContentParent已經建立完成了,但是show方法裡面經過層層調用最終調用到了mWindow.requestFeature(Window.FEATURE_NO_TITLE);這個方法,它裡面裡有這麼一個判斷mContentParent != null的時候就抛出異常,由于mContentParent在onCreate前就已經存在了是以,當調用requestFeature的時候就會破除異常,是以不可以在show()之前調用setContentView方法。

同理如果要需要設定mWindow.requestFeature那就必須要在onCreate之前去掉調用,也就是在show之前去調用,不也會因為mContentParent已經存而導緻抛出異常;

至于LayoutParams的設定時期在這裡就不多講了,在這裡寫連結内容 就已經講述過了。

下面就繼續說說第二種自定義dialog的方式:

第二種自定義dialog的方式就是繼承dialog或者alertDialog,下面就看一個例子:

public class StockDialog extends AlertDialog implements View.OnClickListener {
    private Context context;
    private TextView title, tv1, tv2;
    private EditText ed1, ed2, ed3;
    private Button btn;
    private ImageView img;

    private BasicVHolder holder;

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

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

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

        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    }

    public void setTitle(String str) {
        holder.setText(R.id.stock_info_dialog_title, str);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.stock_info_dialog_img:
                dismiss();
                break;
            case R.id.stock_info_dialog_btn:
                String str1 = ((EditText) holder.getChildeView(R.id.stock_info_dialog_ed1)).getText().toString();
                String str2 = ((EditText) holder.getChildeView(R.id.stock_info_dialog_ed2)).getText().toString();
                String str3 = ((EditText) holder.getChildeView(R.id.stock_info_dialog_ed3)).getText().toString();
                dismiss();
                break;
        }
    }
}
           

上面這個例子是通過繼承AlertDialog來實作自定義dialog需求的,下面就講一下其中需要注意的地方,首先繼承AlertDialog如果你需要設定dialog的Feature(調用requestFeature)的話,那麼久不許要注意requestFeature的調用時期了,要在super.onCreate之前調用,至于為什麼這裡就不在細講了,上面已經接紹過了,但是如果你繼承的是dialog,就可以不必再super.onCreate方法前調用,因為dialog中的onCreate方法是一個空方法。

如果繼承AlertDialog來自定義那麼如果需要用到EditText的話,那麼還需要加上上這麼一句:

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

FLAG_NOT_FOCUSABLE 表示window不會攔截所有的焦點

FLAG_ALT_FOCUSABLE_IM 表示window不會攔截鍵盤輸入時間的焦點
           

如果不設定這一句的話EditText就無法獲得焦點,也就是無法輸入資訊,那麼到底是為什麼呢?下面就來看看以下這個方法:

private void setupCustomContent(ViewGroup customPanel) {
        final View customView;
        if (mView != null) {
            customView = mView;
        } else if (mViewLayoutResId != ) {
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
        } else {
            customView = null;
        }

        final boolean hasCustomView = customView != null;
        if (!hasCustomView || !canTextInput(customView)) {
            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
        }
        ...
    }
           

從上面的代碼可以看出來一般情況下customView!=null,說以就會給Window設定Flags,來看看setFlags中的傳入參數FLAG_ALT_FOCUSABLE_IM,這個常量表示Window攔截了所有的焦點,說以要想對EditText進行輸入操作,你就必須釋放焦點的攔截,所有需要調用上面的那一句話,如果繼承的是dialog就不需要釋放焦點攔截。

對應繼承父類來實作自定義dialog,如果需設定LayoutParams隻要在setContentView以後調用就可以了,這裡就不詳細說了,閱讀全文就可以知道了。

關于繼承dialog來實作自定義,可以去參考參考一下AlertDialog,關于AlertDialog裡面的實作是通過一個建構者模式來完成AlertDialog的建立過程,AlertDialog.Bulider類把建立過程委托給了AlertController,所有負責的建立操作都是在這個類裡面實作的,Bulider類就相當于一個橋梁的作用。這樣可以似的建構與表現分離開來,可以通過同樣的建構過程建立不同的表現形态。

到這裡,本次的總結就告一段落。希望我的個人記錄可以對其他人也帶來幫助。