上一次總結記錄了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類就相當于一個橋梁的作用。這樣可以似的建構與表現分離開來,可以通過同樣的建構過程建立不同的表現形态。
到這裡,本次的總結就告一段落。希望我的個人記錄可以對其他人也帶來幫助。