手记

Kotlin 超车指南

前段时间在我的知识星球里统计了一下使用kotlin开发的人,感觉还不错,有十多个人在工作中已经正式使用了,我的知识星球《Hi Android》欢迎你的加入,我也应星友的需求写了一部分的kotlin基础知识,我也还在学习的过程中,为了帮助更多的星友认识到Kotlin for Android,所以花了点时间整理了这篇《Kotlin超车指南》,如果对你有所帮助,记得点个赞哦。

推荐我的慕课网Android实战课程,助你暴力提升Android技术。
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固

Kotlin 是一个用于现代多平台应用的静态编程语言,由JetBrains开发。

Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。

Kotlin已正式成为Android官方支持开发语言。

一.Kotlin下载

Kotlin支持命令行,Eclipse,Intellij IDEA,Android Studio 3.0+ 四种方式编程,那么我们学习Kotlin基础,肯定是优先前三者, 只有在开始Android开发的时候才会选择Android Studio

1.命令行

命令行安装的话需要去Github上下载工具包

选择 kotlin-compiler-xxx.zip 即可。

2.Eclipse安装

安装Eclipse之前需要安装JDK,这个没意见吧,然后去Eclipse官网下载:

当然,就像ADT一样,如果想要Eclipse支持Kotlin开发,还需要下载Kotlin的插件

不过离线安装一般都是老版本的Eclipse了,在新版本中可以使用Eclipse商店进行在线安装,点击Eclispe菜单 - help - Eclipse Markerplace 搜索kotlin 就可以了。

3.Intellij IDEA

IDEA安装起来还是很方便的,点击链接:

下载自由版本即可。

4.Android Studio

Android Studio 也是如此,安装即可。

二.Kotlin历史

1.Kotlin名称由来

Kotlin 来源于一个岛屿的名字,全称是 Kotlin Island,是英语「科特林岛」之意。这个小岛属于俄罗斯。

2.Kotlin版本迭代

2010 年 :JetBrains 着手开发 Kotlin。

2011 年 7 月 :JetBrains 公开宣布 Kotlin。

2012 年 2 月 :JetBrains 在 Apache 2 许可证下开源了 Kotlin 的源码

2016 年 2 月 :JetBrains 发布了 Kotlin 1.0 版,算是比较稳定的正式版。

2017 年 :1.1.2(2017 年 6 月)。相比 Kotlin 1.0 版本时,和 Kotlin 有关的代码数目从 210 万行飞升到 1000 多万行。

此后还在不断的更新中…

3.Kotlin语言特点

简洁

简洁时Kotlin最主要的特点。Kotlin中数据类,类型推导,Lambda表达式和函数式编程都可以大大减少代码行数,使得代码更加简洁。

安全

kotlin和Java一样都是静态类型语言

Kotlin支持非空和可空类型,默认情况下Kotlin数据类型声明的变量都是不能接受空值(null)的。

类型推导

Kotlin编译器可以根据变量所在的上下文环境推导出它的数据类型

支持函数式编程

作为现代计算机语言Kotlin支持函数式编程,函数式编程优点:代码变得简洁,增加线程安全和便于测试。

支持面向对象

Kotlin支持函数式编程,但也不排除面向对象。

Java具有良好的互操作性

Kotlin和Java具有100%互操作性,Kotlin不需要任何转换成包装就可以调用Java对象。反之亦然,Kotlin完全可以使用现有的Java框架或库

免费开源

Kotlin源代码时开源免费的,它采用Apache2 许可证。

三.Kotlin基础

1.输出

fun main(arg:Array<String>){
	println("Hello Kotlin!")
}

main函数是我们的主程序入口,在控制台输出Hello Kotlin使用了println方法。

2.输入

有输出就有输入,不过输入是从外部传递的

fun main(arg:Array<String>){
	println("请输入...")
	var a = readLine()
	println("你输入的内容是 $a")
}

3.变量与常量

fun main(arg:Array<String>){
	var name = "张三"
	name = "李四"
	println(name)
	
	val age = 18
	println(age)
}

我们用var代表变量,即可变的量,用val声明常量,即不可变的量

4.数据类型

数据类型和Java是类似的

Byte Short String Int Long Float Double

5.类型推导

类型推导也是kotlin的一个特色,我们来看代码

fun main(arg:Array<String>){
	var name:String
	var age = 19
}

如果没有值就无法类型推导,则需要声明数据类型,如果有值,则可智能推导类型

6.函数

我们先来看下一段示例代码

//输出
fun print(text:String){
	println(text)
}

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

首先是我们的输出函数,实际上所有的函数都有返回值的,但是有些不需要返回值,所有返回了一个Unit类型,也就是我们理解的无返回值,而加法两数相加之后需要把结果返回回去,则有了Int的返回值类型。

a.参数默认值

有时候碰到一些封装的场景,有对函数值统一要求的时候,可以这样处理

fun main(arg:Array<String>){
	println(add(1,2))
	println(add(b = 2))
}

//参数默认值
fun add(a:Int = 5,b:Int):Int{
	return a+b
}

我给a默认赋值了一个5,如果使用默认值,则需要指定b = 2,如果不用此默认值,则直接传值即可,比重载好用。

7.字符串模板

在Java中要拼接字符串的话,需要很多的+号,但是有了字符串模板,一切都会变得简洁起来了

fun main(arg:Array<String>){
	val name = "张三"
	val address = "超市"
	val apple = 10
	val banana = 5
	//张三 去 超市 买了苹果和香蕉一共花了 15 元
	print("$name 去 $address 买了苹果和香蕉一共花了 ${apple + banana} 元")
}

如果用java写,你可以想象要怎么拼接

8.条件判断

条件判断我们有if语句

fun main(arg:Array<String>){
	val i = 10
	if(i < 5){
		println("i比5小")
	}else{
		println("i比5大")
	}
}

9.循环

循环使用in即可,在后面区间中使用

10.空类型

在Java中可能会出现空指针异常,但是在kotlin中是可以避免这种问题的,我们来看下空类型判断

fun main(arg:Array<String>){
	//noNull(null) 报错
	println(yesNull(null))
}

//不能为null
fun noNull(text:String):String{
	return "Hello $text"
}

//可以为null
fun yesNull(i:Int?):String{
	if(i === null){
		return "i == null"
	}
	return "Hello $i"
}

如果我传值指定了是String,那么null作为空类型是无法传入的,如果想要传入null,则需要加上问号?

11.选择表达式

在java中可以使用switch,在kotlin中则是when了

fun printScore(sex:Boolean,i:Int){
	when(i){
		100 -> print("最高成绩")
		80 -> {
			if(sex){
				print("男生这个分数不行")
			}else{
				print("女这个分数还行")
			}
		}
		else -> print("其他人都重考")
	}
}

并且不管我们的if还是when都是有返回值的,默认为表达式的最后一行

fun printScore(sex:Boolean,i:Int){
	val result = when(i){
		100 -> 100
		80 -> {
			if(sex){
				80
			}else{
				90
			}
		}
		else -> 60
	}
	print("result $result");
}

12.区间

区间很好理解,就是某个数到某个数之间,这里来计算一下从0到100的数字之和

fun main(arg:Array<String>){

	var a = 0..100
	var result = 0
	for(num in a){
		result = result + num
	}
	println("从0到100的数字总和: $result")
}

区间还有开区间(a…b)和闭区间[a…b]的概念
闭区间包含了端点的两个值

开区间不包含端点的两个值

13.List

fun main(arg:Array<String>){
	var a = listOf("吃早餐",1,"睡午觉",2)
	for(list in a){
		//取值
		println(list)
	}
	for((k,v) in a.withIndex()){
		//取下标与值
		println("k $k , v $v")
	}
}

我们可以通过listOf来创建list

14.map

键值对的存储,我们先简单理解一下

fun main(arg:Array<String>){
	var map = mapOf("Java" to 86, "Kotlin" to 92, "Go" to 78)
	for((k,v) in map){
		println("k $k , v $v")
	}
}

我们通过mapOf返回一个不可变的map

15.函数式编程

函数式编程是不同于过程式编程的另一种编程范式。函数式编程的思想在许多方面和过程式是冲突的,比如,过程式编程倾向于描述“怎么做”,而函数式编程则更倾向于描述“做什么”,过程式倾向于使用变量,而函数式则倾向于使用常量。尽管如此,函数式和过程式依旧是可以共存的,我们来看一个简单的例子:

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

fun add1(a:Int,b:Int) = a+b
val add2 = {a:Int,b:Int -> a+b}

上一个是标准的函数写法, 而中间,则将表达式作为返回值,而add2则直接声明一个val接收参数计算,这完全就打破了我们之前的编程写法。

16.数据类型转换

类型转换用的最多的就是字符串转Int了,我们看代码:

fun main(arg:Array<String>){
	var a = "12"
	println(a.toInt())
}

其他转换也类型toLong,toString等。

17.递归

递归有两个概念,递归和尾递归,相当于调用自身,我们在java中也学过,再来看下尾递归,尾递归是一个新的概念,他的重要性在于它可以不在调用栈上面添加一个新的堆栈帧,而是更新它,如同迭代一般。

比如我们累加一个超级大的数,如果使用递归会出现堆栈溢出的吗,但是尾递归不会

fun main(arg:Array<String>){
	var result = 0
	println(add(100,result))
}

tailrec fun add(i:Int,result:Int):Int{
	//println("result:$result")
	if(i == 0){
		return result
	} 
	return add(i - 1,result + i)
}

18.单例模式

单例模式在java中有很多写法,但是在kotlin中就简单多了,只需要将class改为object即可,相当于在jvm内存中直接开辟了一块唯一内存地址。

但是使用object只是相当于Java的饿汉式,如:

//Java
public class JavaSingleton {
    private static JavaSingleton instance= new JavaSingleton();
    private JavaSingleton(){

    }
    public static JavaSingleton getInstance(){
        return instance;
    }
}
//Kotlin
object KotlinSingleton

如果我们需要实现双重校验的单例模式的话,需要利用伴生对象和lazy来实现:

//Java
public class JavaSingleton {
    private volatile static JavaSingleton instance;
    private JavaSingleton(){}
    public static JavaSingleton getInstance(){
        if(instance==null){
            synchronized (JavaSingleton.class){
                if(instance==null){
                    instance=new JavaSingleton();
                }
            }
        }
        return instance;
    }
}
//kotlin
class KotlinSingleton private constructor() {
    companion object {
        val instance: KotlinSingleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { KotlinSingleton() }
    }
}

19.枚举类

枚举很简单,稍微看看就行

fun main(args: Array<String>) {
    println("现在是 ${Season.WINTER.name} 位于枚举第 ${Season.WINTER.ordinal} 个元素 ")
}

//季节
enum class Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
}

20.印章类

印章类又称为密封类,用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而它的一个子类可以有可包含状态的多个实例

fun main(args: Array<String>) {
    var a = Son.chinaSon()
    var b = Son.foreignSon()
    a.Hello()
    b.Hello()
}
sealed class Son{
    fun Hello(){
        println("我是孩子")
    }
    class chinaSon:Son()
    class foreignSon:Son()
}

可以看到,假设中国人和外国人在一起生出的孩子,有可能是中国人的样貌,有可能是外国人的样貌取决于基因,那么我通过sealed来修饰。

21.数据类

数据类使用data修饰即可

22.延迟加载

延迟加载在kotlin中有两种,lateinit var 和 by lazy ,在kotlin中,默认属性都是空安全的,所以声明必须有初始化值,但是我们有时候是没有初始值的,这个时候就需要延迟加载了。

lateinit var 只能用于类中

class People {

    //报错
    var names:String
    lateinit var name:String
}

而by lazy 作为惰性加载,其使用第一次的时候才会为你加载

fun main(args: Array<String>) {    
    val p by lazy { People() }
}

我们一般常用的还是lazy,不过需要注意的是
1.by lazy 只能作为val关键字的属性

2.当属性用到的时候才会回调括号内的内容

23.伴生对象

伴生对象的关键字是companion,为外部模拟静态成员,可以半理解为static

class People {
    companion object PeopleInstnce {
        const val name: String = "张三"
    }
}

fun main(args: Array<String>) {
    println("name ${People.name}")
    println("name ${People.PeopleInstnce.name}")

}

要注意的是,每个类只能定义一个伴生对象,这个伴生对象相当于外部类的对象,可以直接通过外部类名访问伴生对象的成员,这也是取消了static的一个弥补手段。

24.内联函数

内联的作用主要还是为性能这块所考虑,我们每一次调用高阶函数的时候,都会创建一个新的对象,所以你可以发现高阶函数都是内联函数,内联函数的关键字是lnline

当一个函数被声明为inline时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方。

四. 面向对象

1.对象

和java一样,我们来看下

class People(var name: String, var sex: Boolean)

fun main(args: Array<String>) {
    var p = People("张三", true);
    println("人类${p.name}他的性别是${if (p.sex) "男" else "女"}")
}

可以看到这里class定义一个类,然后声明他的参数,不需要去创建他的构造函数,实际上,这就是他的构造函数
那么构造函数,这里又有一些区分了,在kotlin中,有主构造和次构造的区别

而我们上述的写法真实情况是这样的,只不过我们一般省略关键字

class People constructor(var name: String, var sex: Boolean)

我们如果没有在主构造中对变量进行var/val的声明,则可以在init语句中进行赋值

class People(name: String, sex: Boolean) {
    private var name: String? = ""
    private var sex: Boolean? = false

    init {
        this.name = name
        this.sex = sex
    }
}

当然,你也可以直接赋值,这也算写法上的演进

class People(name: String, sex: Boolean) {
    private var name: String? = name
    private var sex: Boolean? = sex
}

那么再来看下次构造,在Java中,构造函数是必须和类同名的,但是kotlin中则不是,我们可以这样

class People {

    constructor(name: String)
    constructor(name: String, sex: Boolean)
}

fun main(args: Array<String>) {
    People("张三")
    People("李四", true)
}

2封装

封装和java是一样的,只要不对外提供的代码进行private修饰即可,默认public

3.继承

继承的概念和java类似,但是还是有一些不一样的,我们来看下代码

open class People(var name: String, var sex: Boolean) {
    open fun Eat() {
        println("People Eat")
    }
    open fun Sleep() {
        println("People Sleep")
    }

    open fun Character() {
        println("People Character")
    }
}

class Boy(name: String, sex: Boolean) : People(name, sex) {
    override fun Character() {
        println("Boy Character")
    }
}

fun main(args: Array<String>) {
    val boy = Boy("张三",true);
    boy.Eat()
    boy.Sleep()
    boy.Character()
}

在这段代码中,我定义了一个People,他有吃,睡,性格,三个函数,现在一个男孩继承了他,并且重写了性格的函数,因为每个人的性格都是不一样的,这里和java需要注意的问题就是,在java中我们继承了就可以直接使用父类,但是在kotlin中,父类需要标记open这个关键字才能让人继承,并且子类重写方法需要加上overide

4.抽象

我们看继承贴出的那些代码片段可以发现,其实很多东西我们可以优化的,比如People作为父类没必要实现具体操作,可以抽象出来:

abstract class People(var name: String, var sex: Boolean) {
    abstract fun eat()

    abstract fun sleep()

    abstract fun character()
}

class Boy(name: String, sex: Boolean) : People(name, sex) {
    override fun eat() {
        println("Boy eat")
    }

    override fun sleep() {
        println("Boy sleep")
    }

    override fun character() {
        println("Boy character")
    }
}

fun main(args: Array<String>) {
    val boy = Boy("张三", true);
    boy.eat()
    boy.sleep()
    boy.character()
}

可以看到,我们通过abstract去优化了这个抽象类,而Boy类具体去实现

5.多态

多态我们可以看抽象类的延伸,即同种功能不同的实现

abstract class People(var name: String, var sex: Boolean) {
    abstract fun character()
}

class Boy(name: String, sex: Boolean) : People(name, sex) {
    override fun character() {
        println("$name ${if (sex) "调皮" else "乖巧"}")
    }
}

class Girl(name: String, sex: Boolean) : People(name, sex) {
    override fun character() {
        println("$name ${if (sex) "调皮" else "乖巧"}")
    }

}

fun main(args: Array<String>) {
    val boy = Boy("张三", true);
    boy.character()

    val girl = Girl("李四", false);
    girl.character()
}

比如一个People的性格,男人和女人实现的内容是不一样的。

6.接口

接口使用的关键字是interface,比如我模拟一下点击事件

fun main(args: Array<String>) {

    //点击事件
    click(object :OnClickListener{
        override fun onClick(position: Int) {
            println("position:$position")
        }
    })
}

fun click(listener: OnClickListener){
    listener.onClick(2)
}

interface OnClickListener {
    fun onClick(position: Int)
}

7.委托和代理

委托和代理是相辅相成的,打个比方,政府招商的一块地,一千万委托给了开发商A,这个时候就是政府委托开发商,而开发商代理了政府,那么这个时候开发商A已一百万的价格委托给了开发商B,从中牟利了九百万,也就导致了现在的豆腐渣工程了,那么我们用代码来实现吧。

fun main(args: Array<String>) {

    val build = developersA("一千万")
    build.buildHome()
}

class developersB(var price: String) : IBuildHome {
    override fun buildHome() {
        println("建造房子花费$price")
    }
}

class developersA(var price: String) : IBuildHome by developersB("一百万")

//建造方式的能力
interface IBuildHome {
    fun buildHome()
}

可以看到我们是通过by关键字进行委托的,developersA 获得一千万后给developersB一百万依旧把房子建造出来了。

五.函数式编程

我们在讲kotlin基础的时候实际上是有讲函数式编程的,但是那些都比较粗糙了,这一章节我们讲细一点

1.闭包

我们需要先理解闭包的概念,因为后面讲高阶函数的时候

1.闭包指的是函数的运行环境

2.闭包可以持有函数的运行环境

3.函数内部可以定义函数

4.函数内部也可以定义类

5.在函数中返回一个函数,被返回的函数可以调用主函数的属性

这样可能理解有点抽象,事实上,作为kotlin的特性之一,闭包的用途还是很广泛的,全局变量,顾名思义,其作用域是当前文件甚至文件外的所有地方;而局部变量,我们只能再其有限的作用域里获取。

那么,如何在外部调用局部变量呢?答案就是——闭包,与此给闭包下个定义:闭包就是能够读取其他函数内部变量的函数

fun main(args: Array<String>) {
    val add = add()
    add()
    add()
    add()
    //输出 1 2 3
}

fun add(): () -> Unit {
    var count = 0
    return fun() {
        println(++count)
    }
}

这段代码中可以看到输出的是 1 2 3 ,也就是在return的fun中引用了外部的count

2.forEach

forEach是高阶函数之一,我们先来看下他的用法

fun main(args: Array<String>) {
    val lists = listOf(1,2,3,4,5,6)
    lists.forEach {
        println(it)
    }
}

直接就能打印lists的参数,而要想了解他的工作原理,我们最好来看下他的源码

/**
 * Performs the given [action] on each element.
 */
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

可以看到他是Iterable的扩展函数,接收的是一个action参数,T就是list的类型,也就是

带有Int类型无返回值的函数

而内部则是通过for遍历传入下标得到具体值

还有一个forEachIndexed则是求下标和具体值的,这个自己理解一下

3.apply

apply函数扩展了所有的泛型对象,在闭包范围内可以任意调用该对象的任意方法,并在最后返回该对象.

我们来看一段示例代码

class People {
    val name: String = "小明"
    val age: Int = 18
}

fun main(args: Array<String>) {
    //方式一
    val p1 = People();
    println("p1 ${p1.name} ${p1.age} ")

    //方式二
    People().apply {
        println("p1 $name $age")
    }
}

在闭包内我们可以随意获取和变化参数并且返回对象本身,我们来看下他的源码

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

这段代码中T表示对象本身,并且内部又一个契约contract,用于上下文推断。

4.maxBy

求最大值

fun main(args: Array<String>) {
    val lists = listOf(11,2,30,49,50,6)
    val max = lists.maxBy { it }
    println(max)
}

我们可以传递一个类型进去,他则返回一个类型的最大值,来看下源码

public inline fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null
    var maxElem = iterator.next()
    if (!iterator.hasNext()) return maxElem
    var maxValue = selector(maxElem)
    do {
        val e = iterator.next()
        val v = selector(e)
        if (maxValue < v) {
            maxElem = e
            maxValue = v
        }
    } while (iterator.hasNext())
    return maxElem
}

这段源码实际上就是在遍历获得最大值,通过R返回回去,当然,还有minBy求最小值

5.fifter

过滤函数,要啥RxJava,要啥自行车,kotlin美滋滋

fun main(args: Array<String>) {
    val lists = listOf(11, 2, 30, 49, 50, 6)
    //求大于20且小于50的值
    val filter = lists.filter { (it > 20) and (it < 50)}
    println(filter)
}

这里可以多条件判断,只要符合规范即可。
看源码得知最终调用的是filterTo

6.map

map作为数据转换,根据规则创建新的集合

fun main(args: Array<String>) {
    val lists = listOf(11, 2, 30, 49, 50, 6)
    val filter = lists.map {
        it.toFloat()
    }
    println(filter)
}

这里我对值进行了toFloat的操作,则全部类型变换为float.
看源码得知最终调用的是mapTo,但是可以知道是创建了一个ArrayList进行的返回

7.any

any更多的是作为一个判断函数使用

fun main(args: Array<String>) {
    val lists = listOf(11, 2, 30, 49, 50, 6)
    //集合中是否有30这个值
    val filter = lists.any {
        it == 30
    }
    println(filter)
}

来看下源码

public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return false
    for (element in this) if (predicate(element)) return true
    return false
}

传递的参数是一个T的集合类型返回的是一个布尔的函数,可以看到他的实现也是通过for循环遍历的

8.count

count作为满足条件的统计函数,我们可以这样使用

fun main(args: Array<String>) {
    val lists = listOf(56, 77, 30, 49, 100, 63)
    //统计及格的同学人数
    val filter = lists.count {
        it >= 60
    }
    println(filter)
}

从源码中也可以看出是通过for循环去遍历统计size的

9.find

查找函数,只返回满足条件的第一个

fun main(args: Array<String>) {
    val lists = listOf(56, 77, 30, 49, 100, 63)
    //查找第一个及格的同学
    val filter = lists.find {
        it >= 60
    }
    println(filter)
}

看源码得知最终调用的是firstOrNull

10.groupBy

这是分组函数,用于一个特定条件的分组

fun main(args: Array<String>) {
    val lists = listOf(56, 77, 30, 49, 100, 63)
    //及格和不及格的分组
    val groupBy = lists.groupBy {
        it >= 60
    }
    println(groupBy)
}

比如这个示例,我的条件是大于等60则一组,那么其余为一组,可得及格和不及格的分组信息

11.let

let的作用主要用于避免非空的判断和作用域替换

name?.let{
    //只有不为空才会走进来
}

六.DSL

DSL领域特定语言,即好玩又好吃的语法糖。

1.扩展函数

Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法。在Kotlin源码中,有大量的扩展函数来扩展java,这样使得Kotlin比java更方便使用,效率更高。

我们来举个例子:

fun main(args: Array<String>) {
    val lists = listOf(56, 77, 30, 49, 100, 63)
    println(lists.getIndex())
}
//扩展:获取第一个下标
fun List<Int>.getIndex():Int{
    return get(0)
}

我现在想获取List的第一个值,但是如果List并没有这个方法,我则可以直接给予他扩展。

2.中缀表达式

像我们的and or in 都是中缀表达式,实际上就是省略了点的操作

fun main(args: Array<String>) {
    Boy().love(Girl())
    Girl() love Boy()
}

class Boy{
    fun love(girl: Girl){
        //普通写法
    }
}

class Girl{
    infix fun love(boy: Boy){
        //中缀表达式
    }
}

可以看到,是相当的有趣。

七.Kotlin For Android

关于Kotlin开发Android应用的时候的一些区别

1.创建项目

创建项目的时候,在Configure your peoject 这一项的时候选择kotlin作为开发语言即可

大体的东西都不会变化,只是增加了对Kotlin的支持,包括在project/build.gradle中增加了kotlin的插件

以及在app/build.gradle中增加了kotlin的stdlib和core库,当然还包括了kotlin的扩展库

2.findViewById

kt是不需要自己手动findviewbyid的,我们可以用个扩展插件来完成,先来看一段代码

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mButton.text = "Button"
        mButton.setOnClickListener {
            Log.i("TAG", "Button Click")
        }
    }
}

在这段代码中,我们引入了一段包名

import kotlinx.android.synthetic.main.activity_main.*

我们就可以直接省略初始化的操作,使用id就可以了
这个功能就是kotlin扩展库的功能了

3.跳转界面

//Java
startActivity(new Intent(this,FirstActivity.class))
//Kotlin
startActivity(Intent(this,FirstActivity::class.java))

跳转主要还是在class类上,我们可以看到Kotlin需要::class.java

4.匿名内部类

mButton.setOnClickListener(object :View.OnClickListener{
    override fun onClick(v: View?) {
        //Anything
    }
})
//可简化
mButton.setOnClickListener { 
    
}

5.Anko

这是一个优秀的kotlin辅助库,也可以称之为工具库:

a.代码布局

可以使用它的一些语法糖来实现代码写布局,虽然我觉得这个使用率肯定是不高的

verticalLayout {
    gravity = Gravity.CENTER
    val name = editText()
    val password = editText()
    name.hint = "请输入账号"
    password.hint = "请输入密码"
    button("登录") {
        onClick {
            toast("登录成功")
        }
    }
}

我们来运行看一下

b.Intent 优化

intent跳转也相对的简化了

//需要设置FLAG
startActivity(intentFor<FirstActivity>("id" to 5).singleTop())
//需要传递参数
startActivity<FirstActivity>("id" to 5)
//仅适用跳转
startActivity<FirstActivity>()

c.其他

还有log的优化,以及toast,dialog等,大家自己去发现吧。

八.实战案例:天气预报

这里我准备写一个Kotlin版本的天气预报来给各位演示下一些Koltin语法的基本使用,先来看下效果图

功能也很简单,共四个页面

  • 1.启动页,缩放动画
  • 2.城市选择页,自定义View实现城市选择,RecyclerView与城市选择View双向绑定和联动
  • 3.主页,天气详情,RecyclerView显示五天天气
  • 4.设置页,重新选择城市等

使用的接口是聚合数据

使用的网络请求库是retrofit2,好了,我们开始吧;

1.启动页

启动页就一个动画

这里使用的是ViewCompat自带的动画,追其根源的话最终还是属性动画实现,监听动画结束之后就可以判断是否选择过城市了,选择过就直接进入主页,没有的话就进入城市选择页,这里作为持久化保存,我是通过Kotlin的单例封装的一个SharedPreferences类

2.城市选择页

城市选择页比较麻烦,我们先来定义请求网络的接口

然后就是一层简单的封装了

这里首先是对类进行了object的单例化,然后对retrofit2进行了延迟初始化,当我们加载城市列表成功之后,我们就将数据设置给我们的侧边城市选择View,这里我贴一下核心的代码

首先就是绘制了,将34个城市名称按照View的高度进行平均分配和绘制,然后紧接着就是滑动的处理了

这样,我们就可以很轻松的实现列表的滑动了,但是这里又有一个问题了,那就是联动的问题

a.View -> RecyclerView

先看选择View控制列表的滑动

我这里的做法就是根据他的滑动坐标点,找到对应的城市名,然后根据城市名去过滤城市列表以此来找到index,这样就可以滚动列表了

b.RecyclerView -> View

这个是反向的操作,我们需要监听列表的滚动

得到第一个可见的下标后以此来推敲出城市名,然后去根据城市名得到index,让View去刷新即可

3.主页

主页的操作其实就是按部就班的将请求的数据显示出来

4.设置页

设置页也是一样的,这里多了个粘贴板的操作罢了

5.Gif预览

最后我们来预览下Gif

九.结语

Kotlin之美还有很多,一文很难讲完,但是循序渐进,还是可以看到效果的,这篇文章的初衷还是希望带领大家走进这门语言,虽然Google强调Kotlin First ,但是就目前而言还只是应用App还没设计到Android 源码层的改动,所以还是百花齐放的阶段,留给大家的时间还是有的,希望大家再接再厉吧。

Github地址:

推荐我的慕课网Android实战课程,助你暴力提升Android技术。
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固

写了这么多,点个小爱心不过分吧,嘿嘿。

23人推荐
随时随地看视频
慕课网APP