kotlin学习记录

kotlin

将一个activity的java文件转为kotlin文件

最简单的转化方式

android stutdio支持一键转化的方式,通过快捷键ctrl +alt + shift + k将整个java文件转化为kt文件,不过可能需要自己解决一些代码不一致的冲突,而且有些时候转化后的代码和我们直接写的代码还是有差别的。

环境配置

kotlin库引入

android studio3.0默认支持kotlin,虽然是默认支持kotlin,但也是需要增加一些配置,不过这个as已经给我们做好了智能提示和自动添加的功能。在我们第一次创建出kt文件的时候,将会有一个kotlin的配置提示,我们只需要点击configure,然后选择全局配置,那么as将会在gradle中添加上kotlin的依赖
[图片上传失败…(image-4852fe-1542511738777)]
gradle文件如下所示:
[图片上传失败…(image-17de9f-1542511738777)]
这样我们就使用kotlin的代码库了。

kotlin extension库引入

之前项目中用的是view的绑定库是ButterKnife,就个人而言,还是觉得挺好用的,毕竟绑定View的代码都能自动生成,不过如果在kt文件中,不能直接引入这个库,需要重新引入kotlin中的ButterKnife库https://github.com/JakeWharton/kotterknife,不过我们可以不用这个库了,因为kotlin给我们提供了一套更为简便的库,我们只需要在gradle中引入kotlin-extension,并且在kt文件中import相对应的xml,就能直接通过id的方式引用view。

//在kt文件中
import kotlinx.android.synthetic.main.activity_member_center.*

//在gradle文件中
apply plugin: 'kotlin-android-extensions'

比如说我们在xml中有这样一个View:

 <android.support.v7.widget.RecyclerView
        android:id="@+id/memberRv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/member_center_tool_bar"
        android:layout_centerHorizontal="true" />

这样我们在kt文件中,可以像下面的方式这样调用:

private fun initRv() {
        adapter = MemberInfoAdapter(this, null)
        memberRv.layoutManager = LinearLayoutManager(this)
        memberRv.itemAnimator?.changeDuration = 0
        memberRv.adapter = adapter
    }

这个memberRv就是RecyclerView在xml中指定的id。

一些常用用法

参数定义

修饰符

在kotlin中,声明所有变量都需要3个关键字修饰,var、val和const。在kotlin中是没有final修饰符的,final修饰符在kotlin中其实就相当于val

  • var:主要是用来修饰可变变量的。
  • val:主要是用来修饰不可变变量的,其实也不能说是不可变,更好的称呼是叫做只读变量,val在修饰变量的时候可以不初始化,但是它不能被赋值,在java中,用final修饰的变量必须要初始化,否则编译不能通过。举个val修饰不初始化的例子:
        class a {
        var c = 1;
        val b: String
        fun getB(): String {
            return c > 3 ? "haha" : "xixi"
        }
        
        fun add() {
            c++
        }
        
        }
    
  • const:其实const就和java中的static final一致了,不能放在局部方法中,在编译期间必须确定变量值。
静态变量及静态方法

在java中,静态属性和静态方法只需要一个static属性就能搞定了,但是在kotlin,它有一个单独的块来标识静态块的初始化,companion object:

companion object {
        val intentFrom: String = "intent_extra"
        fun startMemberCenterActivity(context: Context,                 launchMemberCenter: LaunchMemberCenter) {
            var intent = Intent(context, TestAc::class.java)
            intent.putExtra(intentFrom, launchMemberCenter)
            if (context is Activity) {
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
            context.startActivity(intent)
        }
    }

这个代码块有几个需要注意的点,

  • 在kt定义Intent中,class的指定需要已类名::class.java的方式指定,否则在log中会提示找不到activity
  • 在这个静态块中,定义的变量和方法都是静态的
  • 在外部如果需要访问这个块的方法,如果是kotlin文件访问,可以直接通过类名.的方式调用,如果是java文件访问,需要通过类名.companion.的方式调用:
//在java文件中调用
public void start() {
       TestAc.Companion.startMemberCenterActivity(context, this);
   }
//在kt文件中调用
public void start() {
       TestAc.startMemberCenterActivity(context, this);
   }
kotlin的空指针安全

在变量定义时,我们可以通过kotlin特有的方式来标识变量是否可空:

private var adapter: MemberInfoAdapter? = null

只需要的变量类型后面添加?,就表示这个变量是可以为null的,如果不添加,默认这个变量不能为null。一旦我们添加了?,在后续的代码中,如果有引用到这个变量的地方,如果没有做判空处理将会编译不过,这个判空处理还是挺方便的,

 override fun refreshDiscoveryList(mixFindInfoList: MutableList<MixFindInfo>?) {
        adapter?.notifyMixFindInfoChange(mixFindInfoList)
    }

Equality check should be used instead of elvis for nullable boolean check
在调用时,如果adaper为空了,将不会执行后续的操作。
今天遇到了一个符号:

var xie = a ?: ""

这个?:是kotlin的elvis用法,属于两目运算符,就是简单的if、else缩写,
当a不为null,取a的值,当a为null,取""

接口使用

kotlin接口和java接口的使用区别

class TestAc : BaseMvpActivity<MemberCenterPresent>(), MemberCenterPresent.MemberInfoChange,
        KKAccountManager.KKAccountChangeListener, View.OnClickListener {

其中BaseMvpActivity继承,而其他三个都是接口继承。

  • java接口需要用implements关键字实现,而kotlin实现的方式和继承一样
  • java接口能够直接通过匿名内部类创建出来,而kotlin也需要先将接口对象创建出来

when表达式

在kotlin中,用when表达式将将java中的switch替换掉了。
最简单的形式如下:

 override fun onClick(v: View?) {
       when (v?.id) {
           R.id.icBack -> finish()
           R.id.btnOpenLayout -> btnOpenLayoutClick()
           else -> {
           
       }
   }

而且,when里面的条件判断可以加入比较复杂的判断,如下:

when (x) {
   in 1..10 -> print("x is in the range")
   in validNumbers -> print("x is valid")
   !in 10..20 -> print("x is outside the range")
   else -> print("none of the above")
}

继承

kotlin中当含有非空的构造函数得继承,有以下这几种方式:

class MemberCenterAdapter(context: Context?, mixFindInfoList: ArrayList<MixFindInfo>?) : TopicTabListAdapter(context, mixFindInfoList) 

相当于子类的构造器声明就放在了class的定义上,然后会对应父类相应的构造器。还有另外一种实现方式,主要用于多个构造器的声明。

constructor(context: Context?, mixFindInfoList: ArrayList<MixFindInfo>?): super(context, mixFindInfoList)

列表

列表元素访问

在kotlin中,推荐我们使用下标的方式来访问元素,就像在访问数组一样。

private var itemTypeList: ArrayList<Int> = ArrayList()
itemTypeList[2] = ITEM_TYPE_ACTIVITY

列表元素遍历

对于kotlin中的列表遍历,增加了许多遍历方式。最简单的如下所示:

//kotlin 
for (i in startIndex..mixFindInfoList!!.size )
//java
for(int i = startIndex; i < mixFindInfoList.size(); i++)

在kotlin中,推荐我们使用until的方式进行遍历,这样,我们处理起来也就像是在处理流一样。

// a until b表示从a到b进行遍历,并且将遍历的值传递给下游,
// map将上游传递过来的值进行转化,并传递给下游
// forEach对转化后的值进行处理
(startIndex until mixFindInfoList!!.size)
                .map { mixFindInfoList[it] }
                .forEach {
                    itemTypeList.add(it.item_type)
                }

三目运算符

之前在java中用惯了三目运算符,看着kotlin的三目运算符挺不习惯的。

//java, 如果a为true,则取b,否则取c 
a ? b : c
//kotlin, 利用if else组成三目运算符,但是不需要括号
if(a) b else c

函数中流程语句的返回值

在kotlin中,如果多个分支语句都返回值,那么我们可以将返回值放在分支语句的外侧。

//最原始的版本
when (position) {
    0, 2 -> return null
    1 -> return Utility.getSafely(mixFindInfoList, 0)
    else -> {
        return Utility.getSafely<MixFindInfo>(mixFindInfoList, position - 2)
            }
                }

优化后的版本:

return when (position) {
    0, 2 -> null
    1 -> Utility.getSafely(mixFindInfoList, 0)
    else -> {
    Utility.getSafely<MixFindInfo>(mixFindInfoList, position - 2)                }
                }

这种时候,就能够返回每一个when分支对应的值
对于if else语句也能这么使用

 return if (activityListExist()) {
            if (vipBannerExist()) {
                when (position) {
                    0, 2 -> null
                    1 -> Utility.getSafely(mixFindInfoList, 0)
                    else -> {
                        Utility.getSafely<MixFindInfo>(mixFindInfoList, position - 2)
                    }
                }
            } else {
                if (position == 0 || position == 1) {
                    null
                } else {
                    Utility.getSafely<MixFindInfo>(mixFindInfoList, position - 2)
                }
            }
        } else {
            if (position == 0) {
                null
            } else {
                Utility.getSafely<MixFindInfo>(mixFindInfoList, position - 1)
            }
        }
    }

这种时候,每一种判断的分支对应一个值,返回命中的分支对应的值

类型判断和类型转换

java中类型判断和转化:

if (holder instance of MemberVipHeaderHolder) {
    ((MemberVipHeaderHolder)holder).bindData(title, TYPE_VIP_HEADER, isVip, isBtnShow);
}

java中利用instance of进行类型判断,然后直接通过(type)instance进行类型强转,将instance转化为type类型的实例。如果instance不是type类型,那么将会抛出类型转化异常。
而在kotlin中,可以通过as和is关键字进行类型判断和转化

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            ITEM_TYPE_ACTIVITY -> {
                if (holder is MemberAssignViewHolder) {
                    holder.bindData(activityList)
                }
            }
            ITEM_TYPE_MEMBER_INFO_CARD -> {
                if (holder is MemberInfoCardViewHolder) {
                    holder.bindData()
                }
            }
            else -> {
                holder.itemView.setPadding(0, 0, 0, if (position == itemCount - 1) UIUtil.dp2px(40f) else 0)
                if (holder is BaseViewHolder) {
                    holder.holderType = BaseViewHolder.TYPE_MEMBER
                }
                super.onBindViewHolder(holder, position)
            }
        }
    }

利用 a is type 来判断a是否是type类型,如果是type类型的话,那么kotlin将会自动为我们将a转化为type类型,而不需要我们再手动的利用as进行类型转化。如果kotlin直接利用as进行类型转化的话,如果类型不一致或者a为null时,也会抛出异常,但是kotlin提供了类型不一致时的安全转化,如下

val x: String ?= y as? String

上面的例子中,as?是一个安全转化符,如果失败,将会返回null,而不会直接进行类型转化。

lambda

在java中,需要升级到java8才支持lambda表达,而kotlin天生支持lambda表达式,lambda表达式的完成语法如下:

val sum = { x: Int, y: Int -> x + y }

上面的例子定义了一个函数,求得sum = x + y,lambda表达式总是括在大括号中,完整的参数声明和表达式都放在大括号内,函数体在->之后,->之前都是参数,并且可能有类型标注,如果推断出该lambda的返回值类型不是Unit,也就是返回值不为空,那么该lambda主体的最后一个表达式将被视为返回值
下面是某一个lambda的例子:

FindExchangeManager.getInstance().loadExchangeData(mContext, findInfo.discoveryModuleId,
                    holder.adapterPosition, findInfo, FindExchangeManager.ExchangeCallback { data, position, discoveryModuleId ->
                if (Utility.isEmpty(mContext)) {
                    return@ExchangeCallback
                }

                val info = getMixInfoFromPosition(position)
                if (data != null && info != null) {
                    info.topics = data
                    notifyItemChanged(position)
                }
            })

如果在lambda表达式中调用return,将会直接return掉整个上层函数,因为lambda表达式只是一个代码块,虽然看起来功能像是函数,不过也不能说在lambda表达式中不能够return,只要给return指定一个标签,就会结束掉标签所指定的代码块,就像上面的例子,这个lambda表达式是在ExchangeCallback中使用的,如果要结束这个lambda,那么只需要在lambda中return代码添加上@ExchangeCallback标签就行,就绘结束掉ExchangeCallback的这个方法。

ViewHolder中使用kotlin extension

kotlin extension的使用需要先导入相应的xml

//在上一篇的activity中
import kotlinx.android.synthetic.main.activity_member_center.*

//在这个viewHolder中
import kotlinx.android.synthetic.main.listitem_assign.view.*

对于这个导包操作,需要注意的是,kotlinx.android.synthetic.main是固定前缀,后面的是当前页面的layout文件名称,如果是activity,只需要再在后面添加上.*,那就能够在activity中直接以id的形式调用对应的View。如果是在viewHolder中,那么还需要添加上.view,在添加.*才能够使用,而且在viewHolder中,需要以其itemView为持有者来调用。

init {
        context = itemView.context
        itemView.btn_get_gift.setOnClickListener(this)
        itemView.btn_not_gift.setOnClickListener(this)
    }

如上所示,利用itemView来持有其中的view的id,并直接调用。
这个init块,通常都是来做类的初始化的,在构造器执行完之后,会调用这个init块,我们如果有什么需要做初始化的,只需要放在这个init块中

String 转其他类型

java中String转long类型的话,需要调用Long.paras(string)进行转化,如果string不是相应的的类型,那么将会抛出异常。而在kotlin中也有这个方法,不过还新增了几个转化的方法:

//如果string不是一个合法的类型,那么将会抛出异常NumberFormatException
string.toLong()

//如果string不是一个合法的类型,那么将会返回null
string.toLongOrNull()

结合rxjava

kotlin中有专门的rx-kotlin库,刚开始并没有引入这个库,所以尝试着还是使用rxjava

Observable.timer(getDelayTime(), TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    getGrabResult(activityId, thirdId)
                })

还是一样的用法,不过好的一点是可以使用lambda表达式了,subscribe({})中间的花括号就是lambda表达式

todo标注

在java中,使用todo标识并不会出现什么问题,在kotlin中,一旦我们实现了接口或者抽象类,那么我们自动生成的实现方法,将会带上kotlin的todo标注

override fun refreshServerTime(time: Long?) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

如果我们没有删除掉todo这行代码,那么kotlin将会直接报错,kotlin.NotImplementedError,因为这个todo是kotlin默认我们将会删除掉的,如果在运行时还存在,那么执行到todo,并不会跳过,而是会执行,然后报错。

kotlin标注

在kotlin中,如果需要在kotlin的高阶函数表达式返回,而不需要直接在外层函数中返回,或者嵌套循环,想从内层循环终止外层循环等,这种时候我们就需要用到kotlin的label了。比如说下面的例子:

private fun canAutoContinue(pos: Int): Boolean {
        if (commonGoodList == null || continueGoodList == null || commonGoodList!!.size < pos) {
            return false
        }
        val commonGood: RechargeGood? = commonGoodList!![pos]

        continueGoodList!!.forEach {
            if (it.upRenewId == commonGood?.id) {
                return true
            }
        }
        print("1111")
        return false
    }

上面的在forEach中,return的执行将会直接将会结束掉canAutoContinue方法,如果我们只想结束掉forEach,我们可以像下面这样做。

private fun canAutoContinue(pos: Int): Boolean {
        if (commonGoodList == null || continueGoodList == null || commonGoodList!!.size < pos) {
            return false
        }
        val commonGood: RechargeGood? = commonGoodList!![pos]

        continueGoodList!!.forEach lit@{
            if (it.upRenewId == commonGood?.id) {
                return@lit
            }
        }
        print("1111")
        return false
    }

如上面的lit label,那么这样return将会结束lit所指定的lable循环,从而继续往下执行。
比如说for循环嵌套

outFor@ for(x in 1..5) {
            for (y in 1..6) {
                if(x == y) {
                    break@outFor
                }
            }
        }

一旦满足了x==y,然么将会直接跳出外层的for循环,否则一般的break,只会中断内层的for循环。要为一个表达式加标签,我们只要在其前加标签即可。
在返回和跳转语句中,可以指定标签来表示结束哪个标签对应的代码段
如果需要在lambda表达式

kotlin 定义属性时的get和set方法

kotlin定义属性时,可以同时定义get和set方法,而不需要再重新定义方法去赋值。

private var delayTime: Long
        private set(value) {
            delayTime = value
        }
        get() {
            return KKConfigManager.getInstance().
                    getConfig(KKConfigManager.ConfigType.GET_RECHARGE_ORDER_DELAY)
                    .toLongOrNull() ?: 1000
        }

而且,可以添加上访问限定符。而这个get和set方法的设置,主要是通过代码中的位置来标示修饰的哪个变量。

kotlin的幕后字段

Kotlin 中的类不能有字段, 然而我们在有时在自定义访问器时,也就是get和set方法时,需要又一个幕后字段,这个字段是kotlin提供的,field,我们可以直接使用。

var counter = 0 // 注意:这个初始器直接为幕后字段赋值 set(value) {
if (value >= 0) field = value }


val nameHash:Int = 3
        get() {
            field = 5
            return 10
    }

有可能我们需要临时缓存变量的值,因为可能其变量的值可能和返回的值不一致,所以一旦我们需要知道变量的值,我们便可以通过幕后字段来访问。

kotlin的幕后属性

幕后属性主要用于外部只能读,内部可以读写的需求下出现的。例如下面的例子:

val counter get() = _counter
private var _counter = 0

这个_counter就是counter的幕后属性,外部只能访问counter,不能访问_counter,而counter的值又是_counter来指定的。

kotlin的扩展

在kotlin中,if else的三目运算符如下所示:

if(a) x else y

而我们也能通过扩展函数来扩展boolean

inline fun <T> Boolean.yes(action: () -> T): Boolean {
    if (this) action()
    return this
}

inline fun <T> Boolean.no(action: () -> T): Boolean {
    if (!this) action()
    return this

这样,我们在kt文件中,只要引入相应的类,定义在文件内,而非class下

import com.kuaikan.community.extend.yes

扩展函数是静态解析的,所以不能够进行重载,这就意味着我们,我们不能重载类原有的方法,比如说collection的add方法等…
对于扩展函数还有一个非常好用的东西,被扩展的类型可以为空,被称为可空接收者

fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数
return toString()
}

kotlin的扩展属性

既然kotlin支持扩展函数,当然也支持扩展属性了。

 val <T> List<T>.lastIndex: Int
    get() = size - 1

这样就给了List的一个属性lastIndex

kotlin的范型

关于out和in这两个关键字,out是用来输出的,也就是生产者,所以只能作为返回类型,相当于java中的extends,用来界定类型上限;in是用来输入的,所以只能作为消费类型,in类似于java中的super,用来界定类型下限

kotlin的单例

在kotlin中,声明单例变得非常简单,只需要一个关键字object用来修饰对象,就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句右边,对象的初始化是线程安全的。如果需要引用该对象,我们只需要通过其名称来调用。

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ......
}
val allDataProviders: Collection<DataProvider> get() = // ......
}

//调用
DataProviderManager.registerDataProvider(......)

伴生对象

类内部的伴生对象的声明如下:

class MyClass { 
    companion object { }
}
val x = MyClass.Companion

请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:

interface Factory<T> { fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass() }
}

如果我们想将伴生对象的成员生成为真正的静态方法和字段,我们我们可以使用@JvmStatic注解

对象表达式和对象声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行(及初始化)的;
  • 对象声明是在第一次被访问到时延迟初始化的;
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

onAttachedToWindow和onDetachedFromWindow

这个是无关kotlin的,只是刚好在写kt代码时遇到的一个问题,
关于onAttachedToWindow和onDetachedFromWindow的触发时机,

onAttachedToWindow就是在这个View被添加到Window时触发的

顾名思义,onAttachedToWindow就是在这个View被添加到Window时触发的,通过了dispatchAttachedToWindow这个方法触发的,那么View被添加到window的时机又是什么,其实就是对应的activity在生命周期onResume的时候调用的,activity对应的view在onResume的时候被添加添加到window。且每一个view都只会被调用一次,父view调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;

onDetachedFromWindow

onDetachedFromWindow方法是在activity的生命周期destroy的时候被调用的,也就是act对应的view从window中移除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;

kotlin类的继承性

java的class默认都是可以继承的,只有在声明为final的class才是不可继承的。而kotlin却是相反的,默认的类都是不可继承的,也就是默认的修饰符为final,只有显式的声明为open的类才是可以继承的。而对于抽象类,java和kotlin默认都是可以继承的,但是子类必须是抽象类或者实现了该类的所有抽象方法。

kotlin扩展函数的java调用方式

kotlin中的扩展函数是非常方便的,刚开始以为在java中不能够调用到kotlin中的扩展函数,后面发现不是的,对于我们定义的扩展函数类,在java中有相应的使用方式

//KotlinExt.kt
fun Int.dp2px(context: Context?): Int {
    val scale = context?.resources?.displayMetrics?.density?.toInt()
    return (this * (scale ?: 2) + 0.5f).toInt()
}
//java中调用
KotlinExtKt.dp2px(2, getContext());

我们只需要import相应的kotlinExt文件,然后就可以调用其中的方法,但使用方式和kotlin有不一致的地方,对于kt的扩展函数的接收者,也就是上面的Int,在java中会被认为是第一个入参, 如果扩展函数接受的是普通参数,那么这个参数就直接作为后续的入参。
如果扩展数据的第二个参数是lambda表达式,那么有java相应的生成方式:

//KotlinExt.kt
inline fun <T> Boolean.yes(action: () -> T): Boolean {
    if (this) action()
    return this
}
//java中调用
KotlinExtKt.yes(true, new Function0<Object>() {
                    @Override
                    public Object invoke() {
                        return null;
                    }
                });

上面的Boolean的扩展yes方法,接收一个高阶函数,kt中用lambda表示,而在java中,需要以一个匿名内部类的方式替代,

kotlin中内部类

在java中,如果定义一个非静态类内部类,默认将会持有外部类的引用,经常会造成外部类无法被回收,导致内存泄漏,而kotlin中,内部类默认不会持有外部类的引用,只有添加上inner关键字修饰,才会持有外部引用。

kotlin 密封类

kotlin的密封类是java所没有的,在kotlin中,如果一个类被标明为密封类,那么其所有的子类都需要在父类中列出,作为密封类的嵌套内部类

kotlin中的惰性集合操作

kotlin中一些集合操作,比如说map和filter,对于我们来说是非常方便的,比如说筛选一个人群的年龄大于30的人的名称。

people.map(it.name).filter{it.age > 30}

但是上面这种方式,在链式调用的链足够长时,将会产生性能问题,因为链式调用的每一个步骤都会创建一个中间集合,用来存储中间结果,也就是说,每一个步骤的结果都存储在另外一个变量中。所以,kotlin给我们提供了另外一种使用方式,来避免创建中间对象。也就是惰性集合。sequencez作为惰性集合操作,可以优化map和filter等操作,将不会生成任何中间对象

people.asSequence().map(people.name).filter(it.startWith("a")).toList()

sequencez会将元数据生成一个流式对象,对于集合的每一个元素利用iterator遍历,执行map和filter,筛选出符合条件的数据。
惰性集合的操作包含了中间操作和末端操作,中间操作就是转换操作,末端操作就是筛选操作,在上面的例子里面,map是中间操作,filter是末端操作,如果没有末端操作,那么,中间操作也不会执行,既然这样,如果我们末端操作停止了,中间操作也会停止,也就是说,并不会保证所有的中间操作都能够执行。

butterknife库的使用

在java中,butterknife库是一个众所周知的代替findViewId操作的库,如果我们直接在kotlin中想要使用butterknife这个库,那还是需要一些额外的操作的。

引入KotterKnife或者自己提供extension

这个是butterknife作者提供的另外一个kotlin版本的替代库。使用方式如下:

val updateTime by bind<TextView>(R.id.update_time)

虽然我没看KotterKnife内部实现,但是应该跟我们自己提供扩展函数的实现方式是一样的,我们可以通过自己提供扩展函数来设置做这些操作:

fun <T : View> Activity.bind(@IdRes idRes: Int): Lazy<T> {
    return lazy { findViewById<T>(idRes) }
}

fun <T : View> View.bind(@IdRes idRes: Int): Lazy<T> {
    return lazy { findViewById<T>(idRes) }
}

fun <T : View> android.support.v7.widget.RecyclerView.ViewHolder.bind(@IdRes idRes: Int): Lazy<T> {
    return lazy { this.itemView.findViewById<T>(idRes) }
}

fun <T : View> Fragment.bind(@IdRes idRes: Int): Lazy<T?> {
    return lazy { this.view?.findViewById<T>(idRes) }
}

这个扩展数据其实就是代替我们执行findViewById操作,虽然我们是在初始化的时候定义了View的对象,但是它并不会马上执行,lazy关键字修饰的对象将会等到使用时再执行相应方法。也就是说,这个findViewById将会在setContentView之后执行,因为我们使用这个对象肯定是在其之后的。

仍然使用butterknife

其实在kotlin中,我们还是可以使用butterknife的,只是我们需要将butterknife的注解解释器替换为kotlin annotation Processor tool,这样,在kt文件中就能够使用butterknife的注解了,我们不需要担心会对之前的注解产生影响,因为kapt是兼容了annotationProcessor的。

annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
====>
kapt 'com.jakewharton:butterknife-compiler:8.4.0'

替换了kapt之后,就可以进行第二步了,在kt中利用laterinit修饰对象,来保证view对象可以在通过butterknife库赋值

 @BindView(R.id.user_v_layout)
    lateinit var vLayout: ImageView
    @BindView(R.id.comment_user_icon)
    lateinit var userIconIV: ImageView
    @BindView(R.id.comment_user_name)
    lateinit var userNameTV: TextView

猜你喜欢

转载自blog.csdn.net/xiexie_lovely/article/details/84196927