美文网首页Kotlin
step by step Kotlin实现ButterKnife

step by step Kotlin实现ButterKnife

作者: 姜大牙 | 来源:发表于2016-01-15 16:02 被阅读0次

欲善其事,必利其器。想要花式的使用一门语言,惊呆小伙伴,必须从实践入手。知易行难,本文会带领大家一步步的实现Kotlin版本的ButterKnife,深入的体会下Kotlin的魅力。

属性

Kotlin的属性是通过getter和setter来进行访问。完整的属性声明方式为

var <propertyName>: <PropertyType> [ = <property_initializer> ]
    <getter>
    <setter>

但一般情况下,getter和setter是可以省略。值得注意的是,一定不要在getter和setter方法里操作自己,这会造成死循环的~ 为了避免这种情况的发生,Kotlin提供field关键字用于属性的访问器,可以直接使用field来访问自身

var NotNullSingleString : String? = null
    get(){
      return field ?: throw IllegalStateException("not initialized")
    }
    set(value) {
        if(field == null) field = value else throw IllegalStateException("already initialized")
    }

而这样设计的好处在于,getter和setter方法很方便的被定义,可以更直接的对属性进行包装。当然,OO思想深入人心的今天,不抽象都不好意思说学过面向对象。欣慰的是,对此Kotlin提供一套更完善,优雅的解决方案——代理属性

代理属性

val view : TextView by Bind(R.id.text)

class Bind<V>(val id: Int) : ReadOnlyProperty<Activity, V> {
  override fun getValue(thisRef: Activity, property: KProperty<*>): V {
    return thisRef.findViewById(id) as V
  }
}

当然ReadOnlyProperty有一个兄弟接口ReadWriteProperty,还需要实现setValue(thisRef: R, property: KProperty<*>, value: T)。这两个接口分别对应val和var关键字。KProperty是属性的metadata。
细心的小伙伴们应该发现了上面代码问题,每次调用view的时候都需要去findViewById,囧。Kotlin提供了Lazy代理来解决这个问题。Lazy代理,提供的是一个线程安全的getter代理,在属性第一次被调用时,Lazy的Lambdas会被执行,来给属性进行初始化,在之后调用时,此属性将不再被初始化。

 val view : TextView by lazy {
    findViewById(R.id.text) as TextView
  }

提到Lambdas,这个众多语言的语法糖,被广大程序猿喜爱。下面我们就来看一下Kotlin的Lamdas和函数。

函数和Lambdas

函数在Kotlin中尤为重要,也是Kotlin的重要特色之一,我们先来尝点甜头

fun add(a : Int, b : Int) : Int {
    return a + b
}

fun add(a : Int, b : Int) = a + b

这个两个函数是完全一样的,我们可以很方便的省略掉返回值类型和括号。当然这还不够,我们可以传入一个Lambdas

fun compose<A, B, C>( f: (B) -> C, g: (A) -> B ): (A) -> C {
    return {x -> f(g(x))}
}

val oddLength = compose<String, Int, Boolean>({
    it % 2 === 0
},{
    it.length
})

上面的函数实现了一个函数组合的功能。f : (B) -> C 表示名字为f,只有一个类型为B的参数,返回C。当Lambdas只有一个参数时,可用it代替。当然,有些时候我们不必要传入这么多函数只需要一个,比如集合操作

var list = listOf(1, 2, 3)
list.filter { it % 2 !== 0 }.map { "${it}是一个质素" }

作为动态语言,Kotlin同样支持扩展,回到我们的ButterKnife。上面通过 by lazy的实现方式显然达不到我们想要的效果。同时Lazy持有一个internal constructor(),我们无法直接继承,那么扩展是一个很好方式

public fun <V : View> Activity.bview(id : Int) : Lazy<V>{
  return lazy {
    this.findViewById(id) as V
  }
}

注意:在我们想给一个类添加全局扩展时,扩展方法要放在类的外部,并声明为public
我们可以进一步抽象,给Activiy扩展一个新的属性

private val Activity.viewFinder: Activity.(Int) -> View
    get() = { findViewById(it) }

public fun <V : View> Activity.bview(id : Int) : Lazy<V>{
  return lazy {
    this.viewFinder(id) as V
  }
}
实际上,扩展是静态加载的,它并没有直接去修改类本身,也没有给类直接添加一个成员,只是能让这个类的实例通过 . 的方式访问。

Lambdas作为java8的新特性,被众多java的使用者所知晓,但对还在使用java6的android来说,只能望梅止渴。虽然有开源的解决方案,但在生产环境中使用依旧存在风险。不过openJDK也有可能带来很多新的特性,其中有可能就包含Lambdas。我们先行感受下。

空安全

通过上面的事例,我们大体上实现了ButterKnife的功能,接下来我们进一步对其进行完善。

optional

首先我们要明确一点,可空类型和普通类型是完全不同。如果一定要声明这个类型可空,直接使用?

val v: View = TextView(context)
var v2: View? = null

v.visibility = vi2?.visibility ?: View.GONE     

?:在Kotlin中被称作Elvis operator。当然你也可以使用!!来声明类型非空。同时,非空代理也是个不错的选择,甚至可以说更优秀,他在初始化之前调用都会报错。

 var view : View by Delegates.notNull<View>()

之前,我们给父类添加了一个Lambdas属性viewFinder。但是,他的返回值View是可能为空的。在这里我们需要对其进行处理

public fun <V : View> Activity.bindOptionalView(id: Int)
    : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)

@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
    = Lazy { t: T, desc ->  t.finder(id) as V? }

private val Activity.viewFinder: Activity.(Int) -> View?
  get() = { findViewById(it) }

private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
  private object EMPTY
  private var value: Any? = EMPTY

  override fun getValue(thisRef: T, property: KProperty<*>): V {
    if (value == EMPTY) {
      value = initializer(thisRef, property)
    }
    @Suppress("UNCHECKED_CAST")
    return value as V
  }
}

我们重新声明一个lazy代理来处理。并在view为空的时候什么也不做
致此我们基本完成了一个Kotlin版本的ButterKnife,Jake Wharton的Kotterknife

更多令人兴奋的东西

接口

Kotlin中接口的方法是可以实现的,如同java8中的接口默认方法。但属性是不能初始化的,必须在继承的类中对其初始化。
这给我们提供了一种新的思路去定义接口,只提供属性,不提供方法。比如KClass。
Kotlin的反射不同于java,它提供两种机制,View::class -> KClass View::class.java -> Class

Data Object

Data Classes不同于其他对象,他的声明尤其简单

data class User(var name : String, var age : Int)

通过这种方式,就可以简单的声明一个pojo。同时Kotlin在Java注解方面的兼容非常出色,你可以直接使用Library的注解,例如Gson

data class User(@SerializedName("_id") var id : String, var name : String, var age : Int)

单例

Kotlin提供companion object,伴随对象。在他内部声明的属性和方法可直接通过.的方式访问。但不同于java的静态块,他也是个对象,可以继承接口。并在反射中可以通过KClass.companionObjectInstance得到他的实例

class App : Application() {
    companion object {
        var instance: App by NotNullSingleValueVar()
    }

    override fun onCreate() {
            super.onCreate()
            instance = this
    }
}

private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("${desc.name} " +
                "not initialized")
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = if (this.value == null) value
        else throw IllegalStateException("${desc.name} already initialized")
    }
}

扩展的域

我们可以直接引入某些方法,并在类中进行调用,这就省去了写Utility的麻烦

import foo.bar.goo // 导入所有名字叫 "goo" 的扩展
// 或者
import foo.bar.* // 导入foo.bar包下得所有数据

fun usage(baz: Baz) {
    baz.goo()
}

当然,Kotlin还有很多新奇好玩特性,同时通过实践,最直观的感受就是,他更像一个java的扩展版,一切都以java为基础,去延伸而不是修改,去添加而不是破坏。

Calling Java code from Kotlin

相关文章

  • step by step Kotlin实现ButterKnife

    欲善其事,必利其器。想要花式的使用一门语言,惊呆小伙伴,必须从实践入手。知易行难,本文会带领大家一步步的实现Kot...

  • 框架整理系列十七(牛逼的万能适配器-比十三更牛逼!)

    引用 使用Step 1: 初始化 Step 2: 配置 Step 3: 数据 Kotlin github大神:ht...

  • step by step

    忙毕业的事,一直没有时间和心情去研究彩铅画,难的这几天有空闲时间,就找了画纸,买了彩铅(超市里买的儿童24色彩铅笔...

  • Step by step

    我相信,每一个光鲜亮丽的外表下,都有一段执拗而又孤独的坚持。 如果人生只有一次翻身的机会,那么你一定要拼尽全力。 ...

  • Step by step

    今天是第一天来到简书,关于写作这件事情,始终是蛰伏在心底的。 这个星期四,我在数学课上,忽然想换个微博,想在微博上...

  • step by step

    今天开始,慢慢来。 一切,也都会慢慢的到来❤️

  • Step by step

    ----针对多多少少已经有点工作经验的盆友 补充那些被遗忘、忽略的知识 (重点是面试中曾出现过的问题 ajax、...

  • step by step

    或许有时候你会灵感突袭,猛地想写点东西但是又不知从何下笔。每每写文章时你想好了结构却思考不出该写一个怎样的故事。这...

  • Step by Step

    周末整理了一下,发现还有十多本书未学习。其中很大一部分曾经看过,但是并未整理笔记,因此感觉有些焦躁。加之一天五小时...

  • step by step

    很多书籍和文章都有写作的对象,记录事件,思维逻辑,自己的看法。 最近看到一片文章写给自己的Kids的,俨然一个副业...

网友评论

    本文标题:step by step Kotlin实现ButterKnife

    本文链接:https://www.haomeiwen.com/subject/avyhkttx.html