Kotlin入门(20)几种常见的对话框
提醒对话框
手机上的app极大地方便了人们的生活,很多业务只需用户拇指一点即可轻松办理,然而这也带来了一定的风险,因为有时候用户并非真的想这么做,只是不小心点了一下而已,如果app不做任何提示的话,继续吭哧吭哧兀自办完业务,比如转错钱了、误删资料了,往往令用户追悔莫及。所以对于部分关键业务,app为了避免用户的误操作,很有必要弹出消息对话框,提醒用户是否真的要进行此项操作。这个提醒对话框便是app开发常见的alertdialog,说起这个alertdialog,安卓开发者都有所耳闻,该对话框不外乎消息标题、消息内容、确定按钮、取消按钮这四个要素,使用java编码显示提醒对话框,基本跟下面的示例代码大同小异:
alertdialog.builder builder = new alertdialog.builder(this);
builder.settitle("尊敬的用户");
builder.setmessage("你真的要卸载我吗?");
builder.setpositivebutton("残忍卸载", new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog, int which) {
tv_alert.settext("虽然依依不舍,但是只能离开了");
}
});
builder.setnegativebutton("我再想想", new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog, int which) {
tv_alert.settext("让我再陪你三百六十五个日夜");
}
});
alertdialog alert = builder.create();
alert.show();
显而易见上述代码非常冗长,特别是两个按钮的点击事件,又是匿名类又是函数重载,令人不堪卒读。尝试将以上java代码转换为kotlin代码,则改写后的kotlin代码如下所示:
val builder = alertdialog.builder(this)
builder.settitle("尊敬的用户")
builder.setmessage("你真的要卸载我吗?")
builder.setpositivebutton("残忍卸载") { dialog, which -> tv_alert.text = "虽然依依不舍,还是只能离开了" }
builder.setnegativebutton("我再想想") { dialog, which -> tv_alert.text = "让我再陪你三百六十五个日夜" }
val alert = builder.create()
alert.show()
这下看来点击事件的代码在很大程度上简化了,不过除此之外,整块代码依然显得有些臃肿,尤其是运用了建造者模式的builder类,虽然表面上增强了安全性,但对于编码来说其实是累赘。因此,anko库将其做了进一步的封装,给context类添加了一个扩展函数,即“alert(消息内容, 消息标题) { 几个按钮及其点击事件 }”,简化后的alert弹窗代码举例如下:
alert("你真的要卸载我吗?", "尊敬的用户") {
positivebutton("残忍卸载") { tv_alert.text = "虽然依依不舍,还是只能离开了" }
negativebutton("我再想想") { tv_alert.text = "让我再陪你三百六十五个日夜" }
}.show()
现在的kotlin代码相比之下更方便阅读了,并且代码量还不到原来java代码的三分之一。当然,为了正常地使用这么好的扩展函数,不要忘了在代码文件头部加上下面一行导入语句:
import org.jetbrains.anko.alert
这么精简的kotlin代码,功能上可是一点都没偷工减料的,它的提醒对话框效果与java编码一模一样,都如下图所示。
下拉选择框
对于某些固定值的条件选择,比如红绿蓝三原色选择其一,一月份到十二月份选择其中一个月份等等,这些情况在android中用到了下拉框spinner。界面上的spinner控件一开始是个右侧带向下箭头的文本,点击该文本会弹出一个选择对话框,选中某一项之后,对话框消失,同时界面上的文本替换为刚才选中的文本内容。光看下拉框的功能其实挺简单的,可是若用java代码实现的话,就得费一番功夫了,下面便是spinner控件的调用代码例子:
private void initspinner() {
arrayadapter<string> staradapter = new arrayadapter<string>(this,
r.layout.item_select, stararray);
staradapter.setdropdownviewresource(r.layout.item_dropdown);
spinner sp = (spinner) findviewbyid(r.id.sp_dialog);
sp.setprompt("请选择行星");
sp.setadapter(staradapter);
sp.setselection(0);
sp.setonitemselectedlistener(new myselectedlistener());
}
private string[] stararray = {"水星", "金星", "地球", "火星", "木星", "土星"};
class myselectedlistener implements onitemselectedlistener {
public void onitemselected(adapterview<?> arg0, view arg1, int arg2, long arg3) {
toast.maketext(spinnerdialogactivity.this, "你选择的行星是"+stararray[arg2], toast.length_long).show();
}
public void onnothingselected(adapterview<?> arg0) {}
}
不出所料这再次体现了java编码的尾大不掉,简简单单的功能在java代码中被分解为以下几个专门的处理:
1、首先要定义一个数组适配器arrayadapter,指定待选择的字符串数组,以及每项文本的布局文件;
2、其次要定义一个选择监听器onitemselectedlistener,在用户选中某项时触发,响应文本项的选中事件;
3、最后spinner控件依次设置选择对话框的标题、数组适配器、选择监听器、默认选项等等;
我的天,这也太专业了吧,在产品经理看来,这只是个下拉框而已,有必要搞这么复杂吗?然而java代码就是这么错综复杂,要想开发android,只能这么捣腾,不然还有更好的法子吗?不信的话换成kotlin试试?说时迟那时快,在android studio上面把spinner上述的java代码转换为kotlin,不一会儿就生成了如下的kotlin代码:
private fun initspinner() {
val staradapter = arrayadapter(this, r.layout.item_select, stararray)
staradapter.setdropdownviewresource(r.layout.item_dropdown)
val sp = findviewbyid(r.id.sp_dialog) as spinner
sp.prompt = "请选择行星"
sp.adapter = staradapter
sp.setselection(0)
sp.onitemselectedlistener = myselectedlistener()
}
private val stararray = arrayof("水星", "金星", "地球", "火星", "木星", "土星")
internal inner class myselectedlistener : onitemselectedlistener {
override fun onitemselected(arg0: adapterview<*>, arg1: view, arg2: int, arg3: long) {
toast("你选择的行星是${stararray[arg2]}")
}
override fun onnothingselected(arg0: adapterview<*>) {}
}
瞧瞧,号称终结者的kotlin也不过尔尔,整体代码量跟java相比是半斤八两,丝毫不见了往日的威风。由于这里的java代码逻辑实在拐弯抹角,又是数组适配器又是选择监听器的,因此kotlin对这种玩意确实没有好办法。既然此路不通,那就试试别的办法呗,前面提到spinner其实由两部分组成,一部分是直接显示在界面上的带箭头文本,另一部分是点击后弹出的选择对话框,所以能不能绕过spinner,运用所见即所得的理念,干脆把下拉框分离成两个控件好了。倘若仅仅是一个带箭头的文本,毫无疑问使用文本视图textview就可以了,箭头图标可以在布局文件中通过drawableright属性来指定。于是布局文件中的下面spinner节点:
<spinner
android:id="@+id/sp_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_torightof="@+id/tv_dialog"
android:gravity="left|center"
android:spinnermode="dialog" />
表面上完全可以被下面这个textview节点所取代:
<textview
android:id="@+id/tv_spinner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_torightof="@+id/tv_dialog"
android:gravity="center"
android:drawableright="@drawable/arrow_down"
android:textcolor="@color/black"
android:textsize="17sp" />
如果再来一个选择对话框,这样只要给该文本视图添加点击事件,点击textview弹出选择框,岂不是万事大吉?正巧anko库已经提供了这股东风,与alert一样来自于context的扩展函数,它便是“selector(对话框标题, 字符串队列) { i -> 第i项的选中处理代码 }”,那么将其与前面的文本视图相结合,即可无缝实现原来的下拉框功能,具体的kotlin代码如下所示:
val satellites = listof("水星", "金星", "地球", "火星", "木星", "土星")
tv_spinner.text = satellites[0]
tv_spinner.setonclicklistener {
selector("请选择行星", satellites) { i ->
tv_spinner.text = satellites[i]
toast("你选择的行星是${tv_spinner.text}")
}
}
看看这几行代码,完全不见了数组适配器和选择监听器的踪影,故而代码量一下剧减到对应java代码的三分之一。当然,为了正常地使用selector函数,不要忘了在代码文件头部加上下面一行导入语句:
import org.jetbrains.anko.selector
虽然把布局文件里面的spinner控件换成textview,但是二者在功能使用上是没什么区别的,同样支持点击文本弹出选择框,也同样支持选中某项的回调。改造后下拉框的界面效果如下图所示。
如此方便易用的selector,竟然撇开了数组适配器和选择监听器,那么它又是怎么实现的呢?认真阅读anko库里面的selector源码,发现原来该函数利用了alertdialog的setitems方法,通过setitems方法指定一串文本,并且定义了每项的点击事件,其运行结果竟然与spinner的选择对话框殊途同归。下面给出alertdialog对应selector函数的java实现代码,方便读者理解它的本质:
alertdialog.builder builder = new alertdialog.builder(this);
builder.settitle("请选择行星");
builder.setitems(satellites, new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog, int which) {
toast.maketext(spinnerdialogactivity.this, "你选择的行星是"+stararray[arg2], toast.length_long).show();
}
});
builder.create().show();
进度对话框
app加载网页之类的请求服务端行为,经常属于耗时操作,往往要过好几秒才能加载完毕,在此期间为了减少用户的等待焦灼感,界面需要展示正在加载的动画,一方面避免造成app卡死的错觉,另一方面提示用户耐心等待。这时就用到了进度对话框,在加载开始前弹出进度框,加载结束后关闭进度框,从而改善了加载交互的用户体验。
进度对话框分两种,一种是水平进度对话框,另一种是圆圈进度对话框,下面分别进行介绍。
水平进度对话框
水平进度对话框允许实时刷新当前进度,方便用户知晓已处理的进展百分比。它主要包含几个元素,包括消息标题、消息内容、对话框样式(水平还是圆圈)、当前进度这四种,如果使用java代码实现该对话框,则是很常规的编码风格,具体的java代码例子如下:
progressdialog dialog = new progressdialog(this);
dialog.settitle("请稍候");
dialog.setmessage("正在努力加载页面");
dialog.setmax(100);
dialog.setprogressstyle(progressdialog.style_horizontal);
dialog.show();
水平进度对话框的java编码,看起来十分中规中矩,可是仍然显得拖泥带水,很简单的功能也花费了六行java代码。倘若使用kotlin书写,则借助于anko库只需下面两行代码:
val dialog = progressdialog("正在努力加载页面", "请稍候")
dialog.show()
瞧瞧,水平进度对话框的实现代码顿时变得清爽了许多,其界面效果与java是完全一样的。当然,因为用到了anko库的扩展函数,所以务必在代码头部加上一行导入语句:
import org.jetbrains.anko.progressdialog
在水平进度对话框弹出之后,若想更新水平条的进度值,则可调用以下代码设置当前进度:
dialog.progress = 进度值(取值为0到100)
当进度值达到100,意味着处理完成,此时即可调用对话框对象的dismiss函数关闭对话框,下图展示了水平进度对话框的进度变化效果。
圆圈进度对话框
圆圈进度对话框仅仅展示转圈的动画效果,不支持实时刷新处理进度,自然在编码上比水平对话框会简化一些,可是用java来显示圆圈进度对话框,依旧需要下列的五行代码:
progressdialog dialog = new progressdialog(this);
dialog.settitle("请稍候");
dialog.setmessage("正在努力加载页面");
dialog.setprogressstyle(progressdialog.style_spinner);
dialog.show();
如果用kotlin实现该对话框的话,有了水平进度对话框的的先例,不出意料只需以下的两行kotlin代码就行了:
val dialog = indeterminateprogressdialog("正在努力加载页面", "请稍候")
dialog.show()
注意到上面的kotlin函数采取了前缀indeterminate,该单词意思是“模糊的、不定的”,表示这种对话框的处理进度是不确定的,不像水平进度对话框可以明确指定当前进度,据此开发者能够将progressdialog与indeterminateprogressdialog两个函数区分开。由于该函数同样来自于anko库,因此不要忘了在用到的代码文件头部加入下面这行语句:
import org.jetbrains.anko.indeterminateprogressdialog
kotlin实现的圆圈进度对话框,转圈效果等同于java实现的效果,具体的对话框界面如下图所示。