依赖于注释处理的流行的 Android 框架和库。
通过对代码注解自动生成模板代码。 注解有助于减少冗余代码,让代码
清晰可读,想要了解运行时的代码,可以直接阅读自动生成的源代码。 但所有生成的代码均为 Java 代码而非 Kotlin。
Dagger
@Inject 对构造函数注解,进而创建类的实例。 而 Kotlin 使用更简洁的语法同时声明属性和构造函数参数。 在 Kotlin 中对构造函数进行注解,必须显式使用
constructor 关键字,并在关键字前声明
@Inject。
class Thermosiphon
@Inject constructor(
private val heater: Heater
) : Pump {
// ……
}
注解方法看上去完全相同。 在下面的示例中,@Binds 决定了无论何时需要 Pump,使用都是 Thermosiphon 对象,@Provides 指定了 Heater 的构造方式,@Singleton 则表示 Heater 是全局单例:
@Module
abstract class PumpModule {
@Binds
abstract fun providePump(pump: Thermosiphon): Pump
}
@Module(includes = arrayOf(PumpModule::class))
class DripCoffeeModule {
@Provides @Singleton
fun provideHeater(): Heater = ElectricHeater()
}
@Module-注解的类定义如何提供不同对象。 需要注意的是,作为多参数传递注解参数时,需要显示的使用 arrayOf 进行包装,比如上文示例中的 @Module(includes = arrayOf(PumpModule::class))。
@Singleton
@Component(modules = arrayOf(DripCoffeeModule::class))
interface CoffeeShop {
fun maker(): CoffeeMaker
}
fun main(args: Array<String>) {
val coffee = DaggerCoffeeShop.builder().build()
coffee.maker().brew()
}
Dagger 为 CoffeeShop 所生成的实现,允许你获得一个完全注入的 CoffeeMaker。 DaggerCoffeeShop 的具体代码实现可在 IDE 中查看。
dependencies {
...
annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version"
}
在 Kotlin 中则需要添加 kotlin-kapt 插件激活 kapt,并使用 kapt 替换 annotationProcessor:
apply plugin: ‘kotlin-kapt‘
dependencies {
...
kapt "com.google.dagger:dagger-compiler:$dagger-version"
}
就是这样。 特别提示:kapt 也能够处理 Java 文件,所以不需要再保留 annotationProcessor 的依赖。
ButterKnife
件(Android Studio 内置)具有同样的效果:使用简洁明了的代码替换findViewByid。 除非现在你正在使用 ButterKnife 而且没有迁移计划,那么前者非常值得尝试。
apply plugin: ‘kotlin-kapt‘
dependencies {
...
compile "com.jakewharton:butterknife:$butterknife-version"
kapt "com.jakewharton:butterknife-compiler:$butterknife-version"
}
@BindView(R2.id.title) TextView title;
在 Kotlin 中使用属性而不是直接使用变量。 对属性使用注解:
@BindView(R2.id.title)
lateinit var title: TextView
@BindView 被定义为仅应用于变量字段,而将注解应用于整个属性时,Kotlin 编译器能够理解并且覆盖相应注解的字段。
@OnClick(R2.id.hello)
internal fun sayHello() {
Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show()
}
以上代码表示点击“hello”按钮后的事件响应。 然而在 Kotlin 中使用 lambda 表达式会让代码更加简洁清晰:
hello.setOnClickListener {
toast("Hello, views!")
}
Anko 库默认提供 toast 函数。
Data Binding
开发者以更简洁的方式将应用程序数据与布局界面进行绑定。
android {
...
dataBinding {
enabled = true
}
}
添加 kapt 的依赖后即可与 Kotlin 代码交互:
apply plugin: ‘kotlin-kapt‘
dependencies {
kapt "com.android.databinding:compiler:$android_plugin_version"
}
使用 Kotlin 并不需要修改任何的 xml 文件。 例如,在 data 中使用 variable 来描述可能在布局中使用的变量, 可以使用Kotlin类型声明变量:
<data>
<variable name="data" type="org.example.kotlin.databinding.WeatherData"/>
</data>
现在,可以使用
@{} 语法引用 Kotlin 的属性:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{data.imageUrl}"
android:contentDescription="@string/image" />
值得一提的是,数据绑定表达式语言使用和 Kotlin 相同的语法对属性进行引用:data.imageUrl。 在 Kotlin 中可以使用 v.prop 来替代 v.getProp(),尽管 getProp() 是Java中的方法。 类似的,也可以直接向属性赋值,而不再需要调用setter。
class MainActivity : AppCompatActivity() {
// ……
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.data = weather//等同于 binding.setData(weather)
}
}
在 xml 中绑定监听器,并在运行时对相应操作进行响应:
<Button
android:text="@string/next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startOtherActivity" />
例如在 MainActivity 中定义的 startOtherActivity 方法:
class MainActivity : AppCompatActivity() {
// ……
fun startOtherActivity(view: View) = startActivity<OtherActivity>()
}
本例中使用的效用函数 startActivity 创建一个不带任何数据参数的 intent,并启动一个新的 activity,这些方法都来自于 Anko 库。 若需要添加参数,则调用
startActivity<OtherActivity>("KEY" to "VALUE").
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
// 用 Kotlin 代码写的相同逻辑
button.setOnClickListener { presenter.onSaveClick(task) }
最后一行中 button 由 id 使用 Kotlin Android 扩展插件所引用。 使用该插件作为替代方案,既允许在代码中保持绑定逻辑,同时又具有简洁的语法。
DBFlow
apply plugin: ‘kotlin-kapt‘
dependencies {
kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version"
compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version"
compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version"
}
@Table(name="users", database = AppDatabase::class)
class User: BaseModel() {
@PrimaryKey(autoincrement = true)
@Column(name = "id")
var id: Long = 0
@Column
var name: String? = null
}
对于 DBFlow 而言,除了将已经有功能代码转换为 Kotlin,还能享受到 Kotlin 的特别支持。 例如,将表声明为数据类:
@Table(database = KotlinDatabase::class)
data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null)
DBFlow 定义了一系列符合 Kotlin 语言习惯的扩展功能,这些都可以通过依赖添加:
dependencies {
compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version"
}
该扩展可以通过类似 C# 中的 LINQ 语法方式编写查询语句,使用 lambda 表达式可以编写更简单的异步计算代码。
Auto-Parcel
apply plugin: ‘kotlin-kapt‘
dependencies {
...
kapt "frankiesardo:auto-parcel:$latest-version"
}
@AutoValue
abstract class Address : Parcelable {
abstract fun coordinates(): DoubleArray
abstract fun cityName(): String
companion object {
fun create(coordinates: DoubleArray, cityName: String): Address {
return builder().coordinates(coordinates).cityName(cityName).build()
}
fun builder(): Builder = `$AutoValue_Address`.Builder()
}
@AutoValue.Builder
interface Builder {
fun coordinates(x: DoubleArray): Builder
fun cityName(x: String): Builder
fun build(): Address
}
}
由于 Kotlin 中没有 static 方法,因此相应的方法会在 companion object中生成。 如果仍然需要从 Java 中调用这些方法,需要添加@JvmStatic注解。
$AutoValue_Address`。以上所有经过转换的代码与原生 Java 代码非常相似。