我将会说这会像这个样子……
安卓困在了 Java 6 的炼狱中。
当我刚开始开发安卓的时候,我是一个多年的 C# 工程师。我发现安卓缺少泛型支持(和 Java 的泛型比较而言),没有 lambda 表达式, 我认为在一个语言中应该有的东西这里却有着非常笨拙的语法。8 年后,我仍然在写着非常繁琐的 Java 6。Java 8 已经出来一段时间了,在不改变二进制代码的情况下,如果能使用它的一些功能就太棒了!(这就是说,我非常感谢 retrolambda 。)但是不幸的是,谁知道 Java 8 什么时候会到来呢。
谢天谢地,安卓开发看起来有希望了: Kotlin 。
Kotlin 是 JetBrains 公司提供的安卓应用开发的全新 JVM-compatible 的语言。如果你从来没有听说过这个语言,我强烈推荐你看看 Michael Pardo’s 关于 Kotlin 的演讲 来自 Droidcon NYC 。而且,应用局部布局视图可用通过 Anko 来创建,这是一个用 Kotlin 写的安卓开发的 DSL (Domain-Specific Language) 组件。你可以对 Anko 有一个基本的了解 这里 。
Kotlin,作为一个开发语言相对于 Java 6 来说是全新的。当你习惯了它的语法以后,你会注意到它比 Java 6 要简洁很多。因为 Kotlin 是 JVM-compatible 的,它编译成的 JVM 二进制代码能被安卓理解。
重要提示:这篇文章假设你已经有了对于基本的 Kotlin 和 Anko 的理解。
作为一个新的知识,你应该想试试 Kotlin,但是你还不想深入理解整个项目。在 Kotlin 和 Android Studio 的帮助下,你可以在你的应用里面同时使用 Java 和 Kotlin。我推荐从一个屏幕,一个功能,或者一个简单的定制的空间开始,使用 Kotlin 看看如何编写它们。慢慢地往你的代码中集成 Kotlin,这样可以让你对这个语言有一个测试,然后允许你保持应用的代码不被改动。
幸运的是集成 Kotlin 到现有的安卓应用里面是十分容易的,只需要使用免费的 Android Studio 的 Kotlin 插件。一开始,你需要安装插件。打开 Android Studio 然后选择 Configure > Plugins
。如果你的屏幕下方看不见了,关闭你所有的项目然后 Welcome to Android Studio
窗口会自动出现。
然后选择 Install JetBrains Plugin
如下。
现在搜索 Kotlin
然后按如下步骤安装 Kotlin
插件。Kotlin 主插件就会出现在你的安卓扩展里面。
你现在已经可以开始创建你的第一个用 Kotlin 实现的功能了!
我打算创建一个简单的 to-do 列表的应用。主屏幕有一个 to-do 的待办事项如下:
用户通过点击 FAB (Floating Action Button) 来增加一个 to-do。编辑 to-do, 你需要点击 to-do 本身。这会加载一个编辑界面。编辑界面是我用 Kotlin 和 Anko 来编写的。
Anko 是一个 DSL (Domain-Specific Language), 它是用 Kotlin 写的安卓插件。长久以来,安卓视图都是用 XML 来表述布局的。这个 XML 常常在你的应用里面有多个复制的版本,而且不能重用(有时候能,通过 includes)。在运行的时候,XML 被转换成 Java 表述,这很浪费 CPU 和电池。Anko 允许你能用 Kotlin 来编写视图,在任何的 Activity 或者 Fragment 里(或者一个 [AnkoComponent] (https://github.com/Kotlin/anko#ankocomponent) 里,这是一个表述视图的扩展 Kotlin 文件)。
这里是一个转换成 Anko 的简单 XML 文件。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> <EditText android:id="@+id/todo_title" android:layout_width="match_parent" android:layout_heigh="wrap_content" android:hint="@string/title_hint" /> <!-- Cannot directly add an inline click listener as onClick delegates implementation to the activity --> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/add_todo" /> </LinearLayout>
verticalLayout { var title = editText { id = R.id.todo_title hintResource = R.string.title_hint } button { textResource = R.string.add_todo onClick { view -> { // do something here title.text = "Foo" } } } }
注意上面第一个的布局中的点击监听函数。因为这是 Kotlin,你可以访问其他的视图成员,比如 title
然后在点击监听函数里面使用它。
使用这个 初学者的应用 你可以以一个空白的面板开始。(最终的代码在 这里 )。 这个应用有以下组件:
MainActivity
) 作为应用的简单控制器。 Todo.java
表述 Realm 模型 你现在用 Kotlin 和 Anko 来创建编辑屏。
现在你已经安装了 Kotlin 扩展,你想用 Configure Kotlin in Project
来配置你的应用。在 Android Studio 里面,按下 CMD+SHIFT+A
来打开 action 查找对话框。输入 Kotlin
然后选择 Configure Kotlin in Project
如下:
在这之后,你的 build.gradle
文件会变成 kotlin-android
,并且作为最上层的配置文件,一个 Kotlin sourceSet
被添加了,然后 Kotlin 被加做了你的依赖。
然后你也想把 Anko 加做依赖。你的 build.gradle
文件看起来像这样:
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.donnfelker.kotlinmix" minSdkVersion 16 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false } } packagingOptions { exclude 'META-INF/services/javax.annotation.processing.Processor' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' // Kotlin compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // Anko compile 'org.jetbrains.anko:anko-sdk15:0.8.2' // sdk19, sdk21, sdk23 are also available compile 'org.jetbrains.anko:anko-support-v4:0.8.2' // In case you need support-v4 bindings compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.2' // For appcompat-v7 bindings compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'io.realm:realm-android:0.87.1' compile 'com.github.thorbenprimke:realm-recyclerview:0.9.12' compile 'com.jakewharton:butterknife:7.0.1' compile 'com.android.support:support-v4:23.1.1' } buildscript { ext.kotlin_version = '1.0.0' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } repositories { mavenCentral() }
现在你可以增加编辑屏幕了。
如果 src/main/kotlin/com.donnfelker.kotlinmix/
目录不存在,创建它。你会注意到 kotlin
文件夹变蓝了,这意味着它是一个源文件目录了。
右键点击 /src/main/kotlin/com.donnfelker.kotlinmix/
目录,选择 New > Kotlin File/Class
,然后重命名为 EditFragment
。新文件被创建了,并且只包含包声明。
拷贝下面的代码到 EditFragment
文件里。
package com.donnfelker.kotlinmix import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.EditText import com.donnfelker.kotlinmix.models.Todo import io.realm.Realm import org.jetbrains.anko.* import org.jetbrains.anko.support.v4.UI import org.jetbrains.anko.support.v4.find import java.util.* class EditFragment : Fragment() { val TODO_ID_KEY: String = "todo_id_key" val realm: Realm = Realm.getDefaultInstance() var todo: Todo? = null companion object { fun newInstance(id: String): EditFragment { var args: Bundle = Bundle() args.putString("todo_id_key", id) var editFragment: EditFragment = newInstance() editFragment.arguments = args return editFragment } fun newInstance(): EditFragment { return EditFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) if(arguments != null && arguments.containsKey(TODO_ID_KEY)) { val todoId = arguments.getString(TODO_ID_KEY) todo = realm.where(Todo::class.java).equalTo("id", todoId).findFirst() val todoTitle = find<EditText>(R.id.todo_title) todoTitle.setText(todo?.title) val todoDesc = find<EditText>(R.id.todo_desc) todoDesc.setText(todo?.description) val add = find<Button>(R.id.todo_add) add.setText(R.string.save) } } override fun onDestroy() { super.onDestroy() realm.close() } /** * A private function to create a TODO item in the database (Realm). * * @param title the title edit text. * @param desc the description edit text. */ private fun createTodoFrom(title: EditText, desc: EditText) { realm.beginTransaction() // Either update the edited object or create a new one. var t = todo?: realm.createObject(Todo::class.java) t.id = todo?.id?: UUID.randomUUID().toString() t.title = title.text.toString() t.description = desc.text.toString() realm.commitTransaction() // Go back to previous activity activity.supportFragmentManager.popBackStack(); } }
上面的例子有一些方法: newInstance
, onActivityCreated
, onDestroy
,和 createTodoFrom
。 createTodoFrom
接收两个 EditText
组件作为参数,或者用作创建新的 Todo
或者用作更新一个已存在的条目,所有的事情用一行代码搞定。
var t = todo?: realm.createObject(Todo::class.java)
这会检查看看当前的 todo
值是不是为 null。如果是的,它会创建一个新的 Todo
实例。如果不为 null,它会使用一个本地的作用域的实例。这个本地作用域的实例会在文件开始的 onActivityCreated
方法中实例化。
在 onActivityCreated
里面,fragment 的参数会被检查。如果他们不是 null, Todo
的 id 会从 intent extras 中解析出来,然后从 Realm 中得到 Todo
对象。 todo
条目已经实例化了,标示着 Todo
对象可以被编辑。这时候,视图会被相应的值更新。
你现在可能意识到我们的 Fragment 里面还没有视图。为了增加一个视图,拷贝粘贴下面的代码到 fragment:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { return UI { verticalLayout { padding = dip(30) var title = editText { id = R.id.todo_title hintResource = R.string.title_hint } var desc = editText { id = R.id.todo_desc hintResource = R.string.description_hint } button { id = R.id.todo_add textResource = R.string.add_todo onClick { view -> createTodoFrom(title, desc) } } } }.view }
这里的 Anko 代码创建了一个垂直方向的线性布局( verticalLayout
)。在 verticalLayout
代码段内部,创建了三个安卓的控件 - 两个 editText
视图和一个 button
视图。这里视图的属性都在一行里面设置好了。按钮控件的定义有些有意思的地方。按钮有一个点击监听函数是定义在视图定义文件里面的。在定义按钮之前,有两个参数 title
和 desc
的方法 createTodoFrom
已经被调用了。最后,通过在 AnkoContext ( UI
类)上调用 view
属性来返回视图。
这里的 id
s 被设置为 R.id.<id_name>
。这些 id
s 需要手工在一个加做 ids.xml
的文件里创建,这个文件放在 app/src/main/res/values/ids.xml
。如果这个文件不存在就创建它。文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <item name="todo_title" type="id" /> <item name="todo_desc" type="id" /> <item name="todo_add" type="id" /> </resources>
这个 ids.xml
文件定义了所有能够被安卓应用引用到的各种视图的 id
s。
现在,视图能在屏幕上显示了。唯一剩下的事情就是在用户点击一个条目的时候显示 Fragment。
打开 TodosFragment
然后在 onTodoClick
方法中增加下面的代码:
EditFragment editFragment = EditFragment.Companion.newInstance(task.getId()); getActivity().getSupportFragmentManager() .beginTransaction() .replace(R.id.content_main, editFragment, editFragment.getClass().getSimpleName()) .addToBackStack(editFragment.getClass().getSimpleName()) .commit();
这个 EditFragment
是使用纯 Kotlin 写的,你可以把它当作一个正常的 Java 对象在安卓代码的任何地方非常容易地调用它。
注意 EditFragment.Companion.newInstance
调用?这是必须的,因为 Kotlin 没有静态方法。所以,一个 伴随对象 是完成 Kotlin 中相似功能的必须品。
最后,你需要连接 FAB 来启动 fragment。在 FAB’s 的点击监听函数里面,在 MainActivity
里面,你需要增加下面的代码:
EditFragment editFragment = EditFragment.Companion.newInstance(); getSupportFragmentManager() .beginTransaction() .replace(R.id.content_main, editFragment, editFragment.getClass().getSimpleName()) .addToBackStack(editFragment.getClass().getSimpleName()) .commit();
编译和安装你的应用,然后点击 FAB。这会启动你的应用中的 Kotlin 部分。添加一个 to-do 然后点击 Add
。退回到 to-dos 的列表,点击一个 to-do 然后你可以编辑它。在 Kotlin 里面的按钮文字 EditFagment
会变成 ‘save’。更新 to-do 然后点击 save。
你现在用 Kotlin 创建了一个功能,而你剩下的应用依旧是用安卓中典型的 Java 来工作的。你可以继续在你的 Kotlin 开发的道路上前行或者在你需要它的时候再使用它。
你现在能够使用 Anko 作为你的 Kotlin 的视图机制了。如果你更喜欢 XML,你可以继续使用 XML 布局。例如,你可以把上面的 onCreateView
方法用下面的内容替换:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater?.inflate(R.layout.your_layout, container, false) }
这给你提供了使用 Kotlin 或者 Anko 的灵活性。
祝你的 Kotlin 旅程好运!