为什么我们要学Kotlin语言:
没时间解释了,快上车吧,开始我们的Kotlin之旅
gradle配置,可先配置上anko库,例如:
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.anko:anko-sdk15:$anko_version"
compile "org.jetbrains.anko:anko-support-v4:$anko_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile 'com.android.support:design:24.2.1'
}
既然配置了plugin,那么classpath也要配置一下:
buildscript {
ext.anko_version = '0.8.2'
ext.kotlin_version = '1.0.4'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
}
}
注意:配置好环境了,那么请尽量把Android Studio的版本升到2.2及以上,并安装plugin: File > Settings > Plugin >搜索“Kotlin”
安装插件的时候耐心点,可能网络的原因,需要多次确定才能继续下载,一边撩妹一边安装,两边不能方。
使尽洪荒之力安装好了,我已经很满意了,那就开始我们的开发之旅了,记住,目前只支持单向转换,每次只能转换一个class到kt,要是怕刚入手不小心搞黄项目的,可以先在新的project里练手,快捷键:crol+shift+alt+k
相信写惯了java的筒子们一看到Kotlin代码有点似曾相识的感觉,其实就相当于java语言的扩展,毕竟java属于后端语言,而kotlin更适合我们安卓前端的开发
如何定义一个类,用class关键字即可
class MainActivity{
}
而Kotlin的类,有一个默认构造函数,只需要在类名后面加上我们的参数:
class MainActivity(val name: String,val id :Int){
init{
...
}
}
如果类中没有内容的话可以省略大括号,而构造函数的函数体,可以写在init块中,咦,怎么基本类型跑到冒号后面呢,后面会提到。那我们需要继承一个类及实现view呢,看下面:
class MainActivity():AppCompatActivity(),CommonView{
}
接下来看我们Kotlin的函数(相当于java中的方法),只需要加上fun关键字即可,而覆写类会在fun前加上override
fun loadImage(){
}
如果函数需要返回类型:
fun add(x:Int,y:Int):Int{
return x+y
}
在Kotlin中分号;不是必须的,也不会报错,少写了分号是不是感觉很斯国一,减少了Androud编程中最多的符号。如果上面的函数可以用一条表达式完成的话,就可以简化大括号了
fun add(x:Int,y:Int):Int= x+y
之前我们注意到参数的写法和我们java的不太一样,基本类型写到冒号后面了,这就是Kotlin的设计特点,它的世界全是对象,因此我们是先写参数再写类型
同时我们可以了解一个Kotlin类型很强大的特点, 例如String类型:
"My name is ${user.name}"
假如我们有一个变量,而$符号就可以在字符串里插入表达式,这样是不是比我们的java显得阔以啊?
类与函数就是我们程序员的刀剑,代码风格的简化可以给我们减少大量的代码冗余,所谓磨刀不误砍柴工,入门新语言的时候,希望能耐心的对待每一个脚步的学习积累。
新入手的筒子们可以观摩Kotlin的一门入门书籍练手基础,基本的知识就不多做解释了http://github.com/wangjiegulu/kotlin-for-android-developers-zh/blob/master/SUMMARY.md。 Kotlin小乐园QQ群:479109923
之前 已经添加了anko库的plugin和依赖,已经可以直接使用了,anko是用Kotlin语言编写的支持库, 但是我们目前还不能使用太多anko里面的内容,他能帮我们简化代码布局,实例化Intent,Activity之间的跳转等等,在以后的学习中,希望能穿插进去
首先我们来看看如何简化Activity的跳转,我们需要从MainActivity,跳转到DetailActivity,并需要Intent传递两个参数,用anko库提供了reified函数如下:
DetailActyivity.class:
companion object {
val ID = "DetailActivity:id"
val CITY_NAME = "DetailActivity:cityName"
}
companion object相当于我们java中的静态static,但与java不同的是它会实例化对象,而里面的对象会随类生,随类似,也就是我们的伴生
而val是不可变变量(具体参考上面链接书籍),相当于java的public static final ,Kotlin中建议变量一般使用val,因为不可变显得更加可控。
再看看MainActivity.class中的代码
startActivity(DetailActivity.ID to it.id,
DetailActivity.CITY_NAME to result.city)
假如我们网络请求后得到一个POJO类(JavaBean)result
此时我们跳转Activity时只需将id和city字段返回结果传参过去,就替代了Intent的实例化,这就是Activity跳转的anko代码
但是我们有疑问的是it哪来的?没错他就是Kotlin的亲儿子,能力相当于java的this,但又比java更强大,可以代替指定对象,但是在传参是它只能代替一个对象,相当于左边的参数,也就是result。
见识过anko库第一个用法之后,接下来再看我们使用最多的findViewById
而在anko库中已经封装好了
val button by lazy{find
我们来剖析他,首先find<>里面是控件的类型,()内是id的名字,这样就完成了一次fvb,那么by lazy是什么鬼?它相当于java中的懒加载,在有些变量我们想在我们使用的时候再初始化,这时他就派上用场了,比java考虑的周全吧,节省了内存。它只能用在不可变变量val,因此是线程安全的。
anko库我就不想说太多了,毕竟也是用Kotlin语言写得,在代码布局方面也有一定的优越性,而我用的更多的是它的其他功能,以后会穿插的介绍。毕竟上个月谷歌刚推出了约束布局ConstraintLayout的正式版,又多了一样装逼的技能。
上一节我们说到了如何用anko库实现fvb,就是一白痴化操作,下面更神奇的地方来了,还记得我们添加了一个extensions的plugins吗,奇了怪了,这是啥用的,简单来说,它就是为了节省fvb而生的的。写了这么多的java代码,是不是感觉到怀疑人生,一部分的时间都用来findviewbyid,即使有了黄油刀注解还有fvb的插件,但还是觉得很占地方。此时它就是你的救世主了,只需要手动导入以下import头
import kotlinx.android.synthetic.main.activity_main.*
其实只要你加载layout然后输入控件id的时候,它就自动导入了
下面我们来实现简单的效果,我想让一个 TextView去设置文字:
"@+id/tx_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
tx_view.text("helloKotlin")
这样一句就完成了?不可思议,相信你的钛合金眼没看错,就是so easy,fvb找控件也省了, setText也之间简化成.text,这也是Kotlin的亮点之一。
当然,有亮点肯定有痛点,如果不同布局两个id命名相同冲突的话就有点恶心了,所以用惯了mvp模式的筒子们不如尝试下用dataBinding,毕竟是谷歌爸比推出的东西,而且实现了双向绑定,同样可以使用Kotlin的.text,这何尝不是一件快事呢。
https://github.com/YeungKC/Gank.io-for-Kotlin-Android
在这里我不想像其他博主那么将一个demo硬生生的拉进来,毕竟开发这种东西随机应变,只要掌握了方法就是造轮子。
至于github上的demo也不少,大可自己去搜索,不要道德绑架别人给你写你要的demo,先掌握好你的基础,再去慢慢实战,例如github.com/YeungKC/Gank.io-for-Kotlin-Android。
回到正题,相信大家都开始用上了RecyclerView了吧,亮点自然不少,最亮的还是自定义LayoutManager, 痛点呢最常见的当然是没有时间点击还有上拉加载,在这里我跟大家讲讲如何在Kotlin写RecyclerView的条目事件点击。一群水军又沸腾起来了,说了那么多不就是写接口嘛,还能怎样,错了,虽然Kotlin里也有interface,但是无需用到它。
第一步:声明一个回调监听器
var onItemCheckedListener: ((day: Day) -> Unit)? = null
为什么有Lambda表达式?因为内置了。不想用?要想马儿跑,又要马儿不吃草,大概是这种道理,因为java8的到来,函数式使安卓编程变得更加简洁,别怕,两句话你就能看懂这条表达式,箭头左边是参数,右边是代码块,至于Unit是Kotlin的关键字意思是无返回值。如果没有参数,箭头左边可以是(),或者可以直接省略左边还有箭头,这就是lambda表达式,还不懂,找度娘去温柔乡一下。
第二步,执行回调函数
class Holder(val root: View) : RecyclerView.ViewHolder(root) {
var onCheckListener: ((i: Int) -> Unit)? = null
init {
root.onClick {
onCheckListener?.invoke(adapterPosition)
}
}
}
onCheckListener是个 局部声明,在选中的时候会调用Kotlin的invoke()方法,至于括号内的adapterPosition直接调用的是RecyclerView的getAdapterPosition(),源码如下:
public final int getAdapterPosition() {
if (mOwnerRecyclerView == null) {
return NO_POSITION;
}
return mOwnerRecyclerView.getAdapterPositionFor(this);
}
第三步:RecyclerView创建监听绑定
override fun onCreateViewHolder(p0: ViewGroup?, p1: Int): Holder? {
val holder = Holder(LayoutInflater.from(context).inflate(R.layout.***, p0, false))
holder.onCheckListener = {
onItemCheckedListener?.invoke(dates!![it])
}
return holder
}
此时it代替应该是adapterPosition,在kotlin中,当传参时只有一个参数时,可以代替,而不用写左边的参数。而onItemCheckedListener这个全局声明就是我们Item点击 监听,此时holder.onCheckListener是为了在RecyclerView创建时调用
onItemCheckedListener?.invoke(dates!![it]),刚接触可能有点懵逼,多练手下应该能看懂此处设计的巧妙之处,而大家可能最懵逼的还是这个invoke()方法,在我的理解中调用他就相当于java中的接口,当view持有监听的时候就会回调到invoke方法,而invoke()方法括号里的参数取决于声明时的参数,这时回看第一步,是不是恍然大悟,lambda表达式的入参在这里被调用了。
第四步:View持有的条目点击监听
adapter.onItemCheckedListener = {
...
}
此时实例化RecyclerView的adapter,大括号内就是点击时的界面操作了巧妙之处就是大括号内可以持有第三步invoke(dates!![it])括号内的引用。
看完这几步,是不是感觉天生懵逼难自弃,没关系,慢慢来。
下面又引发疑问了,dates哪来的,为什么后面有!!,先说这个!!吧,就是Kotlin中非空的意思。
接下来看dates:
var dates: Array? = null
set(value) {
field = value
if (currSelect == null && value != null) {
currSelect = value[0]
lastPosition = 0
}
notifyDataSetChanged()
}
这个嘛,不是个神奇的东西,就是我们java中常见的setter,因为Kotlin的变量本身就默认有getter和setter,能给编程少了很多冗杂的代码,但是这里是对setter的覆写,当我们发现我们要重新设置数据源的时候,它就得出现了。
来看看RecyclerView的构造:
class DateAdapter(context: Context, dates: Array? = null) : RecyclerView.Adapter() {
上面我们说过类的构造函数默认在()里面,当我们new这个类的时候,就将数据源传进来,当需要重新设置数据源的时候,set()覆写就派上用场了。
现在明白onItemCheckedListener?.invoke(dates[it])里的dates[it]其实就是回调选中的dates[adapterPosition]数据。上面的代码参照demohttps://github.com/zzhoujay/DailyGank
好了,但是还是不太满意上面的写法,那我们尝试在简化一下它:
class DateAdapter(context: Context, dates: Day,val itemClick: (Day) -> Unit) : RecyclerView.Adapter() {
上面我们说到构造已经将数据源dates传进来了,这次我们的数据源直接以domain的方式传进来,我们增加了一个对象itemClick,如果我们在这里将它配置了,那么传构造的时候就不需要这个参数了,这是Kotlin的一个好处,那我们再改一下初始化的代码:
val holder = Holder(LayoutInflater.from(context).inflate(R.layout.***, p0, false),itemClick)
绑定布局的代码也修改一下:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindForecast(dates[position])
}
这里我们往Holder传了入参itemClick,再看看Holder的代码怎么改:
class Holder(val root: View,val itemClick: (Day) -> Unit) : RecyclerView.ViewHolder(root) {
fun bindForecast(day: Day) {
with(day){
itemView.setOnClickListener{
//itemClick.invoke(this)
//在这里我们可以直接省略invoke方法,这是Kotlin的设计
itemClick(this)
}
}
}
这里的this指的就是day,而with是Kotlin函数扩展中重要的一个操作符,如果没有with,Kotlin无法辨别大括号里的数据源是day,也就是我们无法使用绑定的数据源。这个with接收一个T类型的对象还有一个扩展函数,因为函数可以放在()外,也就是{}里的内容,而this可以访问这个函数代码块里所以public的方法和属性,这也是Kotlin需要关注的重点知识之一。
我们有一个请求回来的数据源result,想到点击弹出一个吐司去展示日期:
val adapter = DateAdapter(this,result){toast(it.data)}
咦,这个吐司?没看错,Kotlin的吐司就是这么简单,一个toast()搞定。
此时你是不是觉得用上lambda表达式简单了很多,省略了接口写法。
这里我们一鼓作气就完成了两种事件点击的方案,厉害了我的哥。
刚才讲到了空安全,就不得不提一下这个?了,为什么要在对象后面加上问号呢,表示如果该对象为空时什么都不做,不为空则执行后面的函数。这节省了我们很多调试空指针的时间,何乐而不为。
我们可能会遇到这种情景,我们确定我们是在用一个非null变量,但是他的类型却是可null的。我们可以使用!!操作符来强制编译器执行可null类型时跳过限制检查。
讲到Kotlin的优点,不得不赞的就是数据类了。虽然现在有gsonformat,但是公司操蛋的后台并不会给你那么规范,有的字段就不给你,有的就给你个null,在解析的时候不得不手写纠正。有了Kotlin,写JavaBean就是一件变得方便的事。
data class User(username:String,id:Int)
一个JavaBean搞定了,啊?这么简单,就是这么简单,除了序列化之类,所以的getter,setter,toString,hashCode已经给你生成了,构造也有了,直接用就行了。
那我要序列化呢,还有Gson将命名对应字段名呢,别方,看下面:
data class Gank(@SerializedName("username")
var UserName: String,
@SerializedName("id")
var UserId: Int,
@SerializedName("createdAt")
var createdTime: Date,
) : Serializable, Parcelable {
constructor(source: Parcel) : this(source.readString(), source.readString(), source.readSerializable() as Date))
override fun describeContents(): Int {
return 0
}
override fun describeContents(): Int {
return 0
}
constructor() : this("", "", Date())
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(UserName)
dest?.writeString(UserId)
dest?.writeSerializable(createdTime)
}
companion object {
@JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
override fun createFromParcel(source: Parcel): Gank {
return Gank(source)
}
override fun newArray(size: Int): Array {
return arrayOfNulls(size)
}
}
}
}
搞定,序列化完成,使用@SerializedName注解字段别忘了添加gson包
说到这里,必须要给你看看Kotlin是怎么面对疾风吧,我觉得这也是用Kotlin最爽的一个地方了,那就是扩展函数:
新建一个Kotlin file作为我们的扩展类,直接把下面的函数丢进去就行了
fun ImageView.loadImg(imageUrl: String) {
if (TextUtils.isEmpty(imageUrl)) {
Glide.with(context).load(R.mipmap.ic_launcher).into(this)
} else {
Glide.with(context).load(imageUrl).into(this)
}
}
接下来神奇的事情发生了,假如我们的RecyclerView Item布局里有一个ImageView控件,id是mImageView,接下来我们要给它设置图片,我们用现在主流的Glide图片框架:
mImageView.loadImg(**)
**处填图片的路径,就这样完成了图片加载,不是吧?加载类我是写在扩展类的,而且连扩展类都没有导入,放心,Kotlin已经帮你完成了,只要任何类的控件属于ImageView类型,都可以无障碍使用这个扩展函数,太爽了吧,比单例还爽。
再爽一下下,Android开发中写得最多的是什么,那可能访问权限能排上号,Kotlin的var是可变变量,val是不可变变量,它们都默认是public的,在java中这个域是公共的,但是在Kotlin是否一样呢?
不是的,有兴趣的小伙伴可以反编译apk来看看其实Kotlin中的public就等于private+getter+setter,是不是震精了。
还没爽完,这篇就暂时写这么多了,后面有待填坑,Kotlin语言的宗旨就在于让java开发者更快的适应,虽然比Scala简单,但是也要考验你的java编程水平。
喝口水平静下我呆萌的心灵,然后广告插播博客,欢迎Kotlin爱好者加入交流群 Kotlin小乐园QQ群:479109923
非爱好者勿入,问安卓基础者勿入,谢谢。