上一次总结记录了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 != 0) {
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类就相当于一个桥梁的作用。这样可以似的构建与表现分离开来,可以通过同样的构建过程创建不同的表现形态。
到这里,本次的总结就告一段落。希望我的个人记录可以对其他人也带来帮助。