使用 Kotlin 和 Anko 的安卓开发入门

使用 Kotlin 和 Anko 的安卓开发入门

我将会说这会像这个样子……

安卓困在了 Java 6 的炼狱中。


ADB Hell

当我刚开始开发安卓的时候,我是一个多年的 C# 工程师。我发现安卓缺少泛型支持(和 Java 的泛型比较而言),没有 lambda 表达式, 我认为在一个语言中应该有的东西这里却有着非常笨拙的语法。8 年后,我仍然在写着非常繁琐的 Java 6。Java 8 已经出来一段时间了,在不改变二进制代码的情况下,如果能使用它的一些功能就太棒了!(这就是说,我非常感谢 retrolambda。)但是不幸的是,谁知道 Java 8 什么时候会到来呢。

谢天谢地,安卓开发看起来有希望了:Kotlin

Kotlin Logo

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,但是你还不想深入理解整个项目。在 Kotlin 和 Android Studio 的帮助下,你可以在你的应用里面同时使用 Java 和 Kotlin。我推荐从一个屏幕,一个功能,或者一个简单的定制的空间开始,使用 Kotlin 看看如何编写它们。慢慢地往你的代码中集成 Kotlin,这样可以让你对这个语言有一个测试,然后允许你保持应用的代码不被改动。

幸运的是集成 Kotlin 到现有的安卓应用里面是十分容易的,只需要使用免费的 Android Studio 的 Kotlin 插件。一开始,你需要安装插件。打开 Android Studio 然后选择 Configure > Plugins。如果你的屏幕下方看不见了,关闭你所有的项目然后 Welcome to Android Studio 窗口会自动出现。

Configure Plugin

然后选择 Install JetBrains Plugin 如下。

Install Plugin

现在搜索 Kotlin 然后按如下步骤安装 Kotlin 插件。Kotlin 主插件就会出现在你的安卓扩展里面。

你现在已经可以开始创建你的第一个用 Kotlin 实现的功能了!

应用

我打算创建一个简单的 to-do 列表的应用。主屏幕有一个 to-do 的待办事项如下:

Example Screenshot

用户通过点击 FAB (Floating Action Button) 来增加一个 to-do。编辑 to-do, 你需要点击 to-do 本身。这会加载一个编辑界面。编辑界面是我用 Kotlin 和 Anko 来编写的。

什么是 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 文件)。

Receive news and updates from Realm straight to your inbox

这里是一个转换成 Anko 的简单 XML 文件。

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>

用 Anko 描述的同样的视图

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 然后在点击监听函数里面使用它。

上手

使用这个 初学者的应用 你可以以一个空白的面板开始。(最终的代码在 这里)。 这个应用有以下组件:

  • 一个 Activity (MainActivity) 作为应用的简单控制器。
  • 一个 RecyclerView 来展示第一屏上的 to-dos(TodosFragment)
  • 一个 Realm 数据库来存储 to-dos
  • 一个 Todo.java 表述 Realm 模型
  • 一个 RecyclerView 的 adapter

你现在用 Kotlin 和 Anko 来创建编���屏。

创建 Kotlin 和 Anko 的应用

现在你已经安装了 Kotlin 扩展,你想用 Configure Kotlin in Project 来配置你的应用。在 Android Studio 里面,按下 CMD+SHIFT+A 来打开 action 查找对话框。输入 Kotlin 然后选择 Configure Kotlin in Project 如下:

Actions

在这之后,你的 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()
}

现在你可以增加编辑屏幕了。

用 Kotlin 增加一个 Fragment

如果 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();
    }

}

上面的例子有一些方法: newInstanceonActivityCreatedonDestroy,和 createTodoFromcreateTodoFrom 接收两个 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 对象可以被编辑。这时候,视图会被相应的值更新。

用 Anko 增加视图

你现在可能意识到我们的 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 视图。这里视图的属性都在一行里面设置好了。按钮控件的定义有些有意思的地方。按钮有一个点击监听函数是定义在视图定义文件里面的。在定义按钮之前,有两个参数 titledesc 的方法 createTodoFrom 已经被调用了。最后,通过在 AnkoContext (UI 类)上调用 view 属性来返回视图。

这里的 ids 被设置为 R.id.<id_name>。这些 ids 需要手工在一个加做 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 文件定义了所有能够被安卓应用引用到的各种视图的 ids。

Java 和 Kotlin 编译

现在,视图能在屏幕上显示了。唯一剩下的事情就是在用户点击一个条目的时候显示 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。

Example

恭喜你,你现在有混合的 Java 和 Kotlin 了!👏

你现在用 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 旅程好运!

例子代码

About the content

This content has been published here with the express permission of the author.


Donn Felker

Donn Felker is the co-host of the Fragmented Podcast and the founder of Caster.IO, a bite-sized Android developer video training site. He’s the curator of the #AndroidDev Digest, a best selling mobile author, international speaker, and consultant on all things Android. He also loves Chipotle.