自定义AlertDialog最佳实践

MellisaSmal 8年前
   <p>AlertDialog是每一个App所必须的控件,在Android4.0之前不可否认官方正版的Dialog很不美观,但是Android4.0之后的holo风格的Dialog已经可以上的了厅堂了,再后来的Material Design设计风格的拟物扁平化Dialog已经是给人耳目一新的感觉。虽然官方UI标准愈加完善,可是部分UI设计师依然是按照苹果iOS的规范设计App的UI,完全忽略不同平台UI包括交互上的差异性,作为开发者也只好苦逼的遵循UI设计的规范,结果就是Android版本的Dialog多数情况下是以自定义UI风格的方式呈现。系统官方的AlertDialog使用了建造者模式,并且可以链式调用相当方便,所以这次从系统中剥离了AlertDialog源码并进行部分改动以便适用于不同风格。</p>    <p>自定义AlertDialog支持如下方式:</p>    <ul>     <li>系统AlertDialog怎么使用该AlertDialog就怎么使用;</li>     <li>支持自定义样式和主题;</li>     <li>支持自定义AlertDialog布局文件;</li>     <li>支持自定义单选复选列表弹框;</li>     <li>支持功能扩展。</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/54ff4ef461396cffb746253a59c46f33.png"></p>    <h3>AlertDialog简介</h3>    <p>一个标准AlertDialog分为三个区域:标题区、内容区和按钮区,并且每一个区域都是可选部分。构建一个AlertDialog是使用的静态内部类Builder,三个区域需要什么就build什么,这就是建造者模式的好处。</p>    <p>一些常用的方法如下:</p>    <ul>     <li><strong>Builder ()</strong> 构造方法,默认一个参数的是Theme.Holo.Light主题,可以说设置两个参数,第二个参数可以传入主题</li>     <li><strong>setTitle ()</strong> 设置标题栏文本;</li>     <li><strong>setIcon ()</strong> 设置标题栏icon;</li>     <li><strong>setIconAttribute ()</strong> 设置特定主题下的icon;</li>     <li><strong>setCustomTitle ()</strong> 设置自定义标题栏;</li>     <li><strong>setMessage ()</strong> 设置弹框内容区域文本;</li>     <li><strong>setItems ()</strong> 设置弹框列表;</li>     <li><strong>setSingleChoiceItems ()</strong> 设置单选模式列表;</li>     <li><strong>setMultiChoiceItems ()</strong> 设置复选模式列表;</li>     <li><strong>setNegativeButton()</strong> 设置左侧按钮;</li>     <li><strong>setNeutralButton ()</strong> 设置中间按钮;</li>     <li><strong>setPositiveButton ()</strong> 设置右侧按钮;</li>     <li><strong>setView ()</strong> 设置自定义内容区域View;</li>     <li><strong>create()</strong> 创建一个AlertDialog;</li>     <li><strong>show ()</strong> 创建并显示一个AlertDialog。</li>    </ul>    <h3>AlertDialog的基本使用</h3>    <p>创建一个标准AlertDialog</p>    <pre>  <code class="language-java">new Builder(this)   .setTitle("title")   .setMessage("hello world")   .setPositiveButton("确定", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).setNeutralButton("中间", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).setNegativeButton("取消", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).show();  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/436a6e5aed88090f72d0109fa2d77015.png"></p>    <p>创建一个无标题栏AlertDialog</p>    <p>只需要将上述标准Dialog中setTitle方法剔除就可以了。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7780015f2e70687f3a778427962b62d8.png"></p>    <p>创建一个深色主题AlertDialog</p>    <p>AlertDialog在创建的时候默认是一个浅色Theme.Holo.Light主题,在构造方法中就已经设置了,如果想要设置深色主题,可以使用两个参数的构造方法。</p>    <pre>  <code class="language-java">protected AlertDialog(Contextcontext) {   this(context, 0);  }     protected AlertDialog(Contextcontext, int theme) {   super(context, resolveDialogTheme(context, theme));   mAlert = new AlertController(context, this, getWindow());  }  static int resolveDialogTheme(Contextcontext, int resId) {   if (resId == 0) {   return R.style.DialogHoloLight;   } else {   return resId;   }  }  </code></pre>    <p>首先创建一个深色主题继承一个默认系统已有的深色Dialog主题。</p>    <pre>  <code class="language-java"><stylename="DialogDark" parent="@android:style/Theme.DeviceDefault.Dialog">   <itemname="android:alertDialogIcon">@drawable/ic_view_headline_white_24dp</item>   <itemname="layout">@layout/alert_dialog_dark</item>  </style>  </code></pre>    <p>这里我们将dialog的布局文件也重新设置了一个,并且设置了该主题下标题栏icon,代码如下:</p>    <pre>  <code class="language-java">new Builder(this,R.style.DialogDark)   .setTitle("title")   .setIconAttribute(android.R.attr.alertDialogIcon)   .setMessage("hello world")   .setPositiveButton("确定", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).setNeutralButton("中间", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).setNegativeButton("取消", new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int which) {   dialog.dismiss();   }   }).show();  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/da02d7ebfe0c04b2719d51112b9c7837.png"></p>    <p>创建一个类似iOS风格的AlertDialog</p>    <p>想要创建iOS风格的Dialog主要是布局文件的排版,圆角布局或者从底部滑入动画,该demo并没有加入动画。先创建一个iOS主题:</p>    <pre>  <code class="language-java"><stylename="DialogIOS" parent="@android:style/Theme.Holo.Light.Dialog">   <itemname="android:alertDialogIcon">@drawable/ic_view_headline_black_24dp</item>   <itemname="layout">@layout/alert_ios</item>   <itemname="android:windowBackground">@drawable/bg_dialog</item>  </style>  </code></pre>    <p>AlertDialog的创建方式跟上面深色主题类似,只需要在构造方法传入主题即可:</p>    <pre>  <code class="language-java">new Builder(this,R.style.DialogIOS)  ...  </code></pre>    <p>如果创建类似QQ从底部滑入动画,只需要在主题中设置一个转场动画:</p>    <pre>  <code class="language-java"><stylename="DialogIOS" parent="@android:style/Theme.Holo.Light.Dialog">   <itemname="android:alertDialogIcon">@drawable/ic_view_headline_black_24dp</item>   <itemname="layout">@layout/alert_ios</item>   <itemname="android:windowBackground">@drawable/bg_dialog</item>   <itemname="android:windowAnimationStyle">@style/DrawerAnim</item>  </style>  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/af467bd51560a1467b15589d9288b5ff.png"></p>    <p>创建一个列表AlertDialog</p>    <p>创建列表对话框,包括单复选模式实际上都是单行文本模式列表,如果想要支持更复杂列表,可以更改源码或者传入一个自定义View。</p>    <pre>  <code class="language-java">new Builder(this)   .setTitle("title")   .setIconAttribute(android.R.attr.alertDialogIcon)   .setItems(getData(), new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int position) {   dialog.dismiss();   }   }).show();  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9ea032de897a3181ab1142d73e1e670c.png"></p>    <p>创建单复选列表AlertDialog</p>    <p>不可否认系统级别的单复选icon很不好看,所以这里使用了自定义布局方式来更改系统内置默认的布局,内部文本列表使用的是CheckedTextView,这个控件一般很少用到,而这里需要更改icon就是需要该控件的checkMark属性。为checkMark属性设置一个选择器,btn_radio_right选择器如下:</p>    <pre>  <code class="language-java"><selectorxmlns:android="http://schemas.android.com/apk/res/android">      <itemandroid:state_checked="false" android:drawable="@color/transparent"/>      <itemandroid:state_checked="true" android:drawable="@drawable/ic_checked"/>  </selector>  </code></pre>    <p>单选模式的布局文件custom_dialog_singlechoice.xml如下:</p>    <pre>  <code class="language-java"><CheckedTextViewxmlns:android="http://schemas.android.com/apk/res/android"      android:id="@android:id/text1"      android:layout_width="match_parent"      android:layout_height="wrap_content"      android:minHeight="?android:attr/listPreferredItemHeightSmall"      android:textAppearance="?android:attr/textAppearanceMedium"      android:textColor="?android:attr/textColorAlertDialogListItem"      android:gravity="center_vertical"      android:paddingStart="16dip"      android:paddingEnd="16dip"      android:checkMark="@drawable/btn_radio_light"      android:ellipsize="marquee"  />  </code></pre>    <p>复选模式布局实现方式跟单选模式类似,这里就不贴出来了。</p>    <pre>  <code class="language-java">    //单选模式      new Builder(this)   .setTitle("title")   .setIconAttribute(android.R.attr.alertDialogIcon)   .setSingleChoiceItems(getData(), 0, new OnClickListener() {   @Override   public void onClick(DialogInterfacedialog, int position) {   dialog.dismiss();   }   }).show();     //复选模式  new Builder(this)   .setTitle("title")   .setIconAttribute(android.R.attr.alertDialogIcon)   .setMultiChoiceItems(getData(), null, new OnMultiChoiceClickListener() {   @Override   public void onClick(DialogInterfacedialog, int position, boolean isCheked) {      }   }).show();  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f1874bff8b720e1dc337e025f62443bf.gif"> <img src="https://simg.open-open.com/show/0dddcea3abfd9876bf6f524337a747fa.gif"></p>    <p>设置自定义View的AlertDialog</p>    <p>自定义View很简单就是使用setView方法,传入一个自定义的View,在开发中自定义View使用频率还是很高的。</p>    <pre>  <code class="language-java">new Builder(this)   .setTitle("title")   .setIconAttribute(android.R.attr.alertDialogIcon)   .setView(View.inflate(this, R.layout.custom_layout, null)).show();  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/34e452df93fccdd68d8029f3786fe6eb.png"></p>    <p>AlertDialog的销毁以及事件的监听</p>    <p>setCancelable(boolean cancelable)中cancelable默认值是true,这时候点击弹框外部区域或者手机返回键Dialog默认会销毁,反之,点击后Dialog不会销毁。</p>    <p>如果在Dialog销毁时还想处理一些逻辑,就需要借助DialogInterface中如下两个监听器:</p>    <pre>  <code class="language-java">interface OnCancelListener {   public void onCancel(DialogInterfacedialog);  }     interface OnDismissListener {   public void onDismiss(DialogInterfacedialog);  }  </code></pre>    <p>Dialog销毁可以调用两个事件,分别对应这两个方法cancel和dismiss,一般情况下开发者不必在乎它们俩的差异性,当想销毁Dialog的时候,直接调用dismiss或者cancel方法,在cancel方法中实际上也调用了dismiss方法。</p>    <pre>  <code class="language-java">public void cancel() {   if (!mCanceled && mCancelMessage != null) {   mCanceled = true;   // Obtain a new message so this dialog can be re-used   Message.obtain(mCancelMessage).sendToTarget();   }   dismiss();  }  </code></pre>    <p>从这里可以看出如果我们设置了setOnDismissListener,调用cancel和dismiss方法都会出发该监听器,但是如果设置setOnCancelListener则只会在使用cancel方法的时候出发,差别就在这里。如果Dialog被用户显式的销毁掉,这将在用户按下”back”键时,或者Dialog显式的调用cancel()(按下Dialog的cancel按钮)时发生,当一个Dialog被销毁时, OnDismissListener将仍然被通知, 但如果你希望在对话框被显示销毁(而不是正常解除)时被通知,则你应该使用setOnCancelListener()注册一个DialogInterface.OnCancelListener。</p>    <p>AlertDialog全屏</p>    <p>AlertDialog默认是不全屏的,如果想设置全屏可以使用如下方式:</p>    <pre>  <code class="language-java">Viewview=View.inflate(this, R.layout.layout_custom01, null);  //必须设置该属性  view.setMinimumWidth(screenWidth);  AlertDialog.Builderbuilder = new Builder(this)   .setTitle("title").setView(view);  AlertDialogdialog=builder.create();  WindowdialogWindow = dialog.getWindow();  //设置屏幕底部居左对齐  dialogWindow.setGravity(Gravity.LEFT | Gravity.BOTTOM);  WindowManager.LayoutParamslp = dialogWindow.getAttributes();  lp.x = 0;  lp.y = 0;  dialogWindow.setAttributes(lp);  dialog.show();  </code></pre>    <h3>小结</h3>    <p>AlertDialog简单使用基本先到这里,有关AlertDialog代码实现机制在以后的文章中再做解析。官方不建议开发者直接使用Dialog来创建弹出框,所以提供了AlertDialog,但是Android3.0以后官方又提供了一个新的类DialogFragment,说DialogFragment拥有比AlertDialog更优的用户体验,因为它可以跟Fragment生命周期绑定,有关DialogFragment使用后续文章也会简单介绍一下,个人认为以简单实用为上,既然AlertDialog可以满足平常开发需要,非特殊需求我还是选择AlertDialog。开发中有时候会遇到一些从底部上滑进入的弹框,如购物车或者支付宝支付信息界面,这种情况下可以在AlertDialog使用自定义动画满足需求,但是由于这种交互非常常见,所以Google官方又提供了一个吊炸天的控件BottomSheetDialog,最先引入大概是在support v23.2支持包中,也同样有跟Fragment绑定的工具是BottomSheetDialogFragment,该控件算是弥补了SlidingDrawer、DrawerLayout、PopupWindow以及AlertDialog的在底部弹框交互上的不足,走到这里我只想说一个词:”shit”,为什么这些控件不早一天出现。</p>    <p> </p>    <p> </p>    <p>来自:http://www.sunnyang.com/642.html</p>    <p> </p>