一觉醒来Kotlin成了Android的新宠儿【附代码】
前言
凌晨的谷歌I/O 2017开发者大会直播你有没有观看呢?安卓虽然已经成为了移动设备第一操作系统,全球采用Android操作系统的激活设备超过了20亿台。不过对于谷歌来说,需要依靠java来做安卓开发一直是一个心病,因为oracle公司因为java和谷歌在安卓系统上的诉讼搞得心力憔悴。
现在好了,谷歌官方正式支持Kotlin,成为官方认可的安卓开发第一语言,而且从Android Studio 3.0开始,将直接集成Kotlin而无需安装任何的插件。
正文
开始做安卓UI开发一直是使用XML文件来实现。虽然理论上,UI可以使用Java语言来实现,但并没有太多的用处。不久前,JetBrains推出了Kotlin,一种面向JVM的现代语言,可以很好的实现安卓UI。
Jetbrains宣称Anko是Android中更快,更轻松的开发风格。Kotlin提供Anko库来作为DSL(领域专用语言)去设计安卓界面,一个简单的例子:
下面的界面由一个图片和一个按钮组成:
使用Anko实现如下:
verticalLayout{ imageView(R.drawable.anko_logo). lparams(width= matchParent) { padding = dip(20) margin = dip(15) } button("Tap to Like") { onClick { toast("Thanks for the love!") } } }
我们定义了一个垂直的线性布局作为容器包含图片和按钮,使用lparams定义了布局的位置信息,由Kotlin的内联函数也实现了按钮的点击事件。
使用Anko的优点:
- 我们可以将UI布局嵌入到代码中,从而使其类型安全。
- 由于我们不用XML编写,所以它增加了效率,因为在分析XML浪费CPU时间。
- 在UI的程序化转换之后,我们可以将Anko DSL片段放入一个函数中。这样便于代码重用。
- 显然,代码更简洁,可读和可掌握性更高。
现在我们使用Anko Layout和Kotlin构建一个to-do app,来列出我们今天需要做的事。
你可以在GitHub上找到这个项目 to-do app
将Anko库添加到Android Studio:
在streamline-android-java-code-with-kotlin去学习如何添加Kotlin到你的安卓项目中,有了Kotlin,我们需要添加Anko依赖在app/build.gradle中,这样我们就可以顺利编译项目了。
compile [size=1em]'org.jetbrains.anko:anko-sdk15:0.8.3' // sdk19,21,23 也可以使用
可以根据你项目的minSdkVersion来添加这个依赖,上面的例子说明15<=minSdkVersion<19,你可以在Anko的GitHub库中找到自己需要的其他Anko依赖库。
我们准备使用下面的依赖库:
compile 'org.jetbrains.anko:anko-design:0.8.3' compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'
在Activity中调用Anko布局:
我们不再使用XML来写布局文件,所以我们不需要XML View,所以也不需要findViewById()方法了。这里我们假设我们的Anko布局类为MainUI,然后我们可以开始写我们的activit内容:
var ui =MainUI() //MainUI类代替了XML布局 ui.setContentView(this) //this代表Activity类
现在我们创建一个Kotlin文件MainActivity.kt,写上如下代码:
class MainActivity : AppCompatActivity() { val task_list = ArrayList<String>() //任务清单表 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) savedInstanceState?.let { val arrayList = savedInstanceState.get("ToDoList") task_list.addAll(arrayList as List<String>) } var adapter=TodoAdapter(task_list) //定义适配器 var ui = MainUI(adapter) //定义将要使用的Anko UI 布局 ui.setContentView(this) //给Activity设置Anko布局 } override fun onSaveInstanceState(outState: Bundle?) { outState?.putStringArrayList("ToDoList", task_list) super.onSaveInstanceState(outState) } }
task_list是ArrayList,将填充ListView的TodoAdapter。MainUI(adapter)是我们的Anko UI文件,它采用TodoAdapter类作为适配器参数。所以,接下来我们再创建一个TodoAdapter类。
用于ListView的TodoAdapter适配器
TodoAdapter类有一个ArrayList<String>类型的list,并且继承了BaseAdapter。所以我们需要重写一下四个方法:
public int getCount()public Object getItem(int i)public long getItemId(int i)public View getView(int i, View view, ViewGroup viewGroup)
在getView()方法中我们需要使用Anko设计一个表元素的布局。
public int getCount()public Object getItem(int i)public long getItemId(int i)public View getView(int i, View view, ViewGroup viewGroup) 在getView()方法中我们需要使用Anko设计一个表元素的布局。 override fun getView(i : Int, v : View?, parent : ViewGroup?) : View { return with(parent!!.context) { //任务数从1开始 var taskNum: Int = i +1 //清单表元素布局 linearLayout { lparams(width = matchParent, height = wrapContent) padding = dip(10) orientation = HORIZONTAL //任务号 textView { id = R.id.taskNum text=""+taskNum textSize = 16f typeface = Typeface.MONOSPACE padding =dip(5) } //任务名 textView { id = R.id.taskName text=list.get(i) textSize = 16f typeface = DEFAULT_BOLD padding =dip(5) } } } }
- 在这个方法中,我们返回一个包含一个horizontalListView布局列表项的视图。这是使用Kotlin的with语法完成的,它允许我们一次在对象实例上调用很多方法。
- 每个列表项包含两个textview用于显示任务号和任务名称。
- linearLayout,textView是扩展功能。扩展功能使我们有能力启用具有新功能的任何类。
- text,textSize,typeface在android.widget.TextView有getter和setter方法,padding是Anko添加的属性。
继续下一步,我们需要定义列表的操作功能。因此,我们需要在TodoAdapter中定义add(String)和delete(Int)方法。add(String)将任务名称作为参数添加到任务中。delete(Int)将任务所在的位置作为参数来删除任务。下面是具体的实现:
//将任务添加到任务清单的方法 fun add(text: String) { list.add(list.size, text) notifyDataSetChanged() //更新数据 } //将任务从任务清单中移除的方法 fun delete(i:Int) { list.removeAt(i) notifyDataSetChanged() //更新数据 }
所以,现在我们设计了列表,我们也可以添加和删除项目到我们的列表中。接下来完成此适配器类的代码。
TodoAdapter(val list: ArrayList<String> = ArrayList<String>()) : BaseAdapter() { override fun getView(i : Int, v : View?, parent : ViewGroup?) : View { return with(parent!!.context) { //taskNum will serve as the S.No. of the list starting from 1 var taskNum: Int = i +1 //Layout for a list view item linearLayout { id = R.id.listItemContainer lparams(width = matchParent, height = wrapContent) padding = dip(10) orientation = HORIZONTAL textView { id = R.id.taskNum text=""+taskNum textSize = 16f typeface = Typeface.MONOSPACE padding =dip(5) } textView { id = R.id.taskName text=list.get(i) textSize = 16f typeface = DEFAULT_BOLD padding =dip(5) } } } } override fun getItem(position : Int) : String { return list[position } override fun getCount() : Int { return list.size } override fun getItemId(position : Int) : Long { //can be used to return the item's ID column of table eturn 0L } //function to add an item to the list fun add(text: String) { list.add(list.size, text) notifyDataSetChanged() } //function to delete an item from list fun delete(i:Int) { list.removeAt(i) notifyDataSetChanged() } }
注意,使用Anko DSL类中必须要导入org.jetbrains.anko.*。
设计项目的外观
Anko为我们提供了在单独的Kotlin类中为Activity使用UI的便利。因此,每个屏幕都可以被认为是Kotlin类的UI-Activity匹配对。这个UI类是通过继承在org.jetbrains.anko包中定义的AnkoComponent<T>接口的功能来实现的。
除了这个接口,JetBrains还提供免费的DSL布局预览功能。下面是Anko DSL布局预览在Android Studio中的样子:
Anko Preview的相应插件可以从这里下载。请注意,在撰写本文时,Android Studio 2.2的Anko DSL 布局预览被列为开源issue。
回到正题,我们接下来设计MainUI类展示所有任务列表。MainUI类继承了AnkoComponent<T>接口,其中T指的是UI的所有者,activity的内容将会是这个UI。在我们的例子中,所有者就是我们已经在上面定义的MainActivity。接下来,在初始化时,我们必须将TodAadapter对象传递给此类,因为此适配器将用于填充列表。所以,MainUI声明变成:
class MainUI(val todoAdapter : TodoAdapter) : AnkoComponent<MainActivity>
现在我们需要重写方法 createView() ,使用 AnkoContext 对象作为参数并返回一个View 类型:
override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) { }
我们在createView() 方法中UI定义返回给所有者即activity,在这里也就是MainActivity,所以接下来写createView() 方法:
Step1-设计首页
最初,首页是空列表。所以,我们有一个textView要求用户创建一天的Todo List:
return relativeLayout { //声明ListView var todoList : ListView? =null //当没有任务时显示textView内容"What's your Todo List for today?" val hintListView = textView("What's your Todo List for today?") { textSize = 20f }.lparams { centerInParent() } }
centerInParent() 是将视图的布局定义为垂直和水平相对中心的辅助方法。因为它是一个todo性质的应用,其本质在于显示任务的列表。所以,我们在这里定义listView:
//listView verticalLayout { todoList=listView { //assign adapter adapter = todoAdapter } }.lparams { margin = dip(5) }
todoAdapter是我们在MainUI类声明中定义成员变量。我们用todoAdapter的值初始化listView的adapter,这是一个TodoAdpater类的对象,将会用于填充列表。
为了帮助用户添加任务,我们在主屏幕的右下方提供了一个Material design风格的floatingActionButton。所以我们使用Anko编程floatingActionButton为:
floatingActionButton { imageResource = android.R.drawable.ic_input_add }.lparams { //设置按钮在屏幕的右下方 margin = dip(10) alignParentBottom() alignParentEnd() alignParentRight() gravity = Gravity.BOTTOM or Gravity.END }
篇幅所限,Step2、Step3请点击左下角“阅读原文”查看全部文章。
最后的想法
我们在开发此to-do app时没有使用任何XML布局资源,但我们能够以类似的风格设计应用程序。Anko从应用程序的activity或fragments中消除了呈现数据的负担,响应用户交互,连接数据库等这些负担。另外,隔离UI和Activity类使得应用程序更接近MVP(Model-View-Presenter)架构。你可以从这里了解Anko的高级功能。