本文阅读大约半小时,写了很久,写到吐血....
前段时间在我的知识星球里统计了一下使用kotlin开发的人,感觉还不错,有十多个人在工作中已经正式使用了,我的知识星球《Hi Android》欢迎你的加入,我也应星友的需求写了一部分的kotlin基础知识,我也还在学习的过程中,为了帮助更多的星友认识到Kotlin for Android,所以花了点时间整理了这篇《Kotlin超车指南》,如果对你有所帮助,记得点个赞哦。
图片Kotlin 是一个用于现代多平台应用的静态编程语言,由JetBrains开发。
Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
Kotlin已正式成为Android官方支持开发语言。
推荐我的慕课网Android实战课程,助你暴力提升Android技术。
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固
https://coding.imooc.com/class/390.html
点击阅读原文直达课程详情
Kotlin支持命令行,Eclipse,Intellij IDEA,Android Studio 3.0+ 四种方式编程,那么我们学习Kotlin基础,肯定是优先前三者, 只有在开始Android开发的时候才会选择Android Studio
命令行安装的话需要去Github上下载工具包
https://github.com/JetBrains/kotlin/releases/tag/v1.3.61
选择 kotlin-compiler-xxx.zip 即可。
安装Eclipse之前需要安装JDK,这个没意见吧,然后去Eclipse官网下载:
https://www.eclipse.org/downloads/
图片当然,就像ADT一样,如果想要Eclipse支持Kotlin开发,还需要下载Kotlin的插件
https://marketplace.eclipse.org/content/kotlin-plugin-eclipse
不过离线安装一般都是老版本的Eclipse了,在新版本中可以使用Eclipse商店进行在线安装,点击Eclispe菜单 - help - Eclipse Markerplace 搜索kotlin 就可以了。
图片IDEA安装起来还是很方便的,点击链接:
https://www.jetbrains.com/idea/download/
下载自由版本即可。
Android Studio 也是如此,安装即可。
https://developer.android.google.cn/studio
Kotlin 来源于一个岛屿的名字,全称是 Kotlin Island,是英语「科特林岛」之意。这个小岛属于俄罗斯。
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 多万行。
此后还在不断的更新中…
简洁是Kotlin最主要的特点。Kotlin中数据类,类型推导,Lambda表达式和函数式编程都可以大大减少代码行数,使得代码更加简洁。
kotlin和Java一样都是静态类型语言
Kotlin支持非空和可空类型,默认情况下Kotlin数据类型声明的变量都是不能接受空值(null)的。
Kotlin编译器可以根据变量所在的上下文环境推导出它的数据类型
作为现代计算机语言Kotlin支持函数式编程,函数式编程优点:代码变得简洁,增加线程安全和便于测试。
Kotlin支持函数式编程,但也不排除面向对象。
Kotlin和Java具有100%互操作性,Kotlin不需要任何转换成包装就可以调用Java对象。反之亦然,Kotlin完全可以使用现有的Java框架或库
Kotlin源代码是开源免费的,它采用Apache2 许可证。
1fun main(arg:Array){
2 println("Hello Kotlin!")
3}
main函数是我们的主程序入口,在控制台输出Hello Kotlin使用了println方法。
有输出就有输入,不过输入是从外部传递的
1fun main(arg:Array){
2 println("请输入...")
3 var a = readLine()
4 println("你输入的内容是 $a")
5}
1fun main(arg:Array){
2 var name = "张三"
3 name = "李四"
4 println(name)
5
6 val age = 18
7 println(age)
8}
我们用var代表变量,即可变的量,用val声明常量,即不可变的量
数据类型和Java是类似的
Byte Short String Int Long Float Double
类型推导也是kotlin的一个特色,我们来看代码
1fun main(arg:Array){
2 var name:String
3 var age = 19
4}
如果没有值就无法类型推导,则需要声明数据类型,如果有值,则可智能推导类型
我们先来看下一段示例代码
1//输出
2fun print(text:String){
3 println(text)
4}
5
6//加法
7fun add(a:Int,b:Int):Int{
8 return a+b
9}
首先是我们的输出函数,实际上所有的函数都有返回值的,但是有些不需要返回值,所有返回了一个Unit类型,也就是我们理解的无返回值,而加法两数相加之后需要把结果返回回去,则有了Int的返回值类型。
有时候碰到一些封装的场景,有对函数值统一要求的时候,可以这样处理
1fun main(arg:Array){
2 println(add(1,2))
3 println(add(b = 2))
4}
5
6//参数默认值
7fun add(a:Int = 5,b:Int):Int{
8 return a+b
9}
我给a默认赋值了一个5,如果使用默认值,则需要指定b = 2,如果不用此默认值,则直接传值即可,比重载好用。
在Java中要拼接字符串的话,需要很多的+号,但是有了字符串模板,一切都会变得简洁起来了
1fun main(arg:Array){
2 val name = "张三"
3 val address = "超市"
4 val apple = 10
5 val banana = 5
6 //张三 去 超市 买了苹果和香蕉一共花了 15 元
7 print("$name 去 $address 买了苹果和香蕉一共花了 ${apple + banana} 元")
8}
如果用java写,你可以想象要怎么拼接
条件判断我们有if语句
1fun main(arg:Array){
2 val i = 10
3 if(i < 5){
4 println("i比5小")
5 }else{
6 println("i比5大")
7 }
8}
循环使用in即可,在后面区间中使用
在Java中可能会出现空指针异常,但是在kotlin中是可以避免这种问题的,我们来看下空类型判断
1fun main(arg:Array){
2 //noNull(null) 报错
3 println(yesNull(null))
4}
5
6//不能为null
7fun noNull(text:String):String{
8 return "Hello $text"
9}
10
11//可以为null
12fun yesNull(i:Int?):String{
13 if(i === null){
14 return "i == null"
15 }
16 return "Hello $i"
17}
如果我传值指定了是String,那么null作为空类型是无法传入的,如果想要传入null,则需要加上问号?
在java中可以使用switch,在kotlin中则是when了
1fun printScore(sex:Boolean,i:Int){
2 when(i){
3 100 -> print("最高成绩")
4 80 -> {
5 if(sex){
6 print("男生这个分数不行")
7 }else{
8 print("女这个分数还行")
9 }
10 }
11 else -> print("其他人都重考")
12 }
13}
并且不管我们的if还是when都是有返回值的,默认为表达式的最后一行
1fun printScore(sex:Boolean,i:Int){
2 val result = when(i){
3 100 -> 100
4 80 -> {
5 if(sex){
6 80
7 }else{
8 90
9 }
10 }
11 else -> 60
12 }
13 print("result $result");
14}
区间很好理解,就是某个数到某个数之间,这里来计算一下从0到100的数字之和
1fun main(arg:Array){
2
3 var a = 0..100
4 var result = 0
5 for(num in a){
6 result = result + num
7 }
8 println("从0到100的数字总和: $result")
9}
区间还有开区间(a..b)和闭区间[a..b]的概念
闭区间包含了端点的两个值
开区间不包含端点的两个值
1fun main(arg:Array){
2 var a = listOf("吃早餐",1,"睡午觉",2)
3 for(list in a){
4 //取值
5 println(list)
6 }
7 for((k,v) in a.withIndex()){
8 //取下标与值
9 println("k $k , v $v")
10 }
11}
我们可以通过listOf来创建list
键值对的存储,我们先简单理解一下
1fun main(arg:Array){
2 var map = mapOf("Java" to 86, "Kotlin" to 92, "Go" to 78)
3 for((k,v) in map){
4 println("k $k , v $v")
5 }
6}
我们通过mapOf返回一个不可变的map
函数式编程是不同于过程式编程的另一种编程范式。函数式编程的思想在许多方面和过程式是冲突的,比如,过程式编程倾向于描述“怎么做”,而函数式编程则更倾向于描述“做什么”,过程式倾向于使用变量,而函数式则倾向于使用常量。尽管如此,函数式和过程式依旧是可以共存的,我们来看一个简单的例子:
1fun add(a:Int,b:Int):Int{
2 return a+b
3}
4
5fun add1(a:Int,b:Int) = a+b
6val add2 = {a:Int,b:Int -> a+b}
上一个是标准的函数写法, 而中间,则将表达式作为返回值,而add2则直接声明一个val接收参数计算,这完全就打破了我们之前的编程写法。
类型转换用的最多的就是字符串转Int了,我们看代码:
1fun main(arg:Array){
2 var a = "12"
3 println(a.toInt())
4}
其他转换也类型toLong,toString等。
递归有两个概念,递归和尾递归,相当于调用自身,我们在java中也学过,再来看下尾递归,尾递归是一个新的概念,他的重要性在于它可以不在调用栈上面添加一个新的堆栈帧,而是更新它,如同迭代一般。
比如我们累加一个超级大的数,如果使用递归会出现堆栈溢出的吗,但是尾递归不会
1fun main(arg:Array){
2 var result = 0
3 println(add(100,result))
4}
5
6tailrec fun add(i:Int,result:Int):Int{
7 //println("result:$result")
8 if(i == 0){
9 return result
10 }
11 return add(i - 1,result + i)
12}
单例模式在java中有很多写法,但是在kotlin中就简单多了,只需要将class改为object即可,相当于在jvm内存中直接开辟了一块唯一内存地址。
但是使用object只是相当于Java的饿汉式,如:
1//Java
2public class JavaSingleton {
3 private static JavaSingleton instance= new JavaSingleton();
4 private JavaSingleton(){
5
6 }
7 public static JavaSingleton getInstance(){
8 return instance;
9 }
10}
11//Kotlin
12object KotlinSingleton
如果我们需要实现双重校验的单例模式的话,需要利用伴生对象和lazy来实现:
1//Java
2public class JavaSingleton {
3 private volatile static JavaSingleton instance;
4 private JavaSingleton(){}
5 public static JavaSingleton getInstance(){
6 if(instance==null){
7 synchronized (JavaSingleton.class){
8 if(instance==null){
9 instance=new JavaSingleton();
10 }
11 }
12 }
13 return instance;
14 }
15}
16//kotlin
17class KotlinSingleton private constructor() {
18 companion object {
19 val instance: KotlinSingleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { KotlinSingleton() }
20 }
21}
枚举很简单,稍微看看就行
1fun main(args: Array) {
2 println("现在是 ${Season.WINTER.name} 位于枚举第 ${Season.WINTER.ordinal} 个元素 ")
3}
4
5//季节
6enum class Season {
7 SPRING,
8 SUMMER,
9 AUTUMN,
10 WINTER
11}
印章类又称为密封类,用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而它的一个子类可以有可包含状态的多个实例
1fun main(args: Array) {
2 var a = Son.chinaSon()
3 var b = Son.foreignSon()
4 a.Hello()
5 b.Hello()
6}
7sealed class Son{
8 fun Hello(){
9 println("我是孩子")
10 }
11 class chinaSon:Son()
12 class foreignSon:Son()
13}
可以看到,假设中国人和外国人在一起生出的孩子,有可能是中国人的样貌,有可能是外国人的样貌取决于基因,那么我通过sealed来修饰。
数据类使用data修饰即可
延迟加载在kotlin中有两种,lateinit var 和 by lazy ,在kotlin中,默认属性都是空安全的,所以声明必须有初始化值,但是我们有时候是没有初始值的,这个时候就需要延迟加载了。
lateinit var 只能用于类中
1class People {
2
3 //报错
4 var names:String
5 lateinit var name:String
6}
而by lazy 作为惰性加载,其使用第一次的时候才会为你加载
1fun main(args: Array) {
2 val p by lazy { People() }
3}
我们一般常用的还是lazy,不过需要注意的是
1.by lazy 只能作为val关键字的属性
2.当属性用到的时候才会回调括号内的内容
伴生对象的关键字是companion,为外部模拟静态成员,可以半理解为static
1class People {
2 companion object PeopleInstnce {
3 const val name: String = "张三"
4 }
5}
6
7fun main(args: Array) {
8 println("name ${People.name}")
9 println("name ${People.PeopleInstnce.name}")
}
要注意的是,每个类只能定义一个伴生对象,这个伴生对象相当于外部类的对象,可以直接通过外部类名访问伴生对象的成员,这也是取消了static的一个弥补手段。
内联的作用主要还是为性能这块所考虑,我们每一次调用高阶函数的时候,都会创建一个新的对象,所以你可以发现高阶函数都是内联函数,内联函数的关键字是lnline
当一个函数被声明为inline时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方。
和java一样,我们来看下
1class People(var name: String, var sex: Boolean)
2
3fun main(args: Array) {
4 var p = People("张三", true);
5 println("人类${p.name}他的性别是${if (p.sex) "男" else "女"}")
6}
可以看到这里class定义一个类,然后声明他的参数,不需要去创建他的构造函数,实际上,这就是他的构造函数
那么构造函数,这里又有一些区分了,在kotlin中,有主构造和次构造的区别
而我们上述的写法真实情况是这样的,只不过我们一般省略关键字
1class People constructor(var name: String, var sex: Boolean)
2
我们如果没有在主构造中对变量进行var/val的声明,则可以在init语句中进行赋值
1class People(name: String, sex: Boolean) {
2 private var name: String? = ""
3 private var sex: Boolean? = false
4
5 init {
6 this.name = name
7 this.sex = sex
8 }
9}
当然,你也可以直接赋值,这也算写法上的演进
1class People(name: String, sex: Boolean) {
2 private var name: String? = name
3 private var sex: Boolean? = sex
4}
那么再来看下次构造,在Java中,构造函数是必须和类同名的,但是kotlin中则不是,我们可以这样
1class People {
2
3 constructor(name: String)
4 constructor(name: String, sex: Boolean)
5}
6
7fun main(args: Array) {
8 People("张三")
9 People("李四", true)
10}
封装和java是一样的,只要不对外提供的代码进行private修饰即可,默认public
继承的概念和java类似,但是还是有一些不一样的,我们来看下代码
1open class People(var name: String, var sex: Boolean) {
2 open fun Eat() {
3 println("People Eat")
4 }
5 open fun Sleep() {
6 println("People Sleep")
7 }
8
9 open fun Character() {
10 println("People Character")
11 }
12}
13
14class Boy(name: String, sex: Boolean) : People(name, sex) {
15 override fun Character() {
16 println("Boy Character")
17 }
18}
19
20fun main(args: Array) {
21 val boy = Boy("张三",true);
22 boy.Eat()
23 boy.Sleep()
24 boy.Character()
25}
在这段代码中,我定义了一个People,他有吃,睡,性格,三个函数,现在一个男孩继承了他,并且重写了性格的函数,因为每个人的性格都是不一样的,这里和java需要注意的问题就是,在java中我们继承了就可以直接使用父类,但是在kotlin中,父类需要标记open这个关键字才能让人继承,并且子类重写方法需要加上overide
我们看继承贴出的那些代码片段可以发现,其实很多东西我们可以优化的,比如People作为父类没必要实现具体操作,可以抽象出来:
1abstract class People(var name: String, var sex: Boolean) {
2 abstract fun eat()
3
4 abstract fun sleep()
5
6 abstract fun character()
7}
8
9class Boy(name: String, sex: Boolean) : People(name, sex) {
10 override fun eat() {
11 println("Boy eat")
12 }
13
14 override fun sleep() {
15 println("Boy sleep")
16 }
17
18 override fun character() {
19 println("Boy character")
20 }
21}
22
23fun main(args: Array) {
24 val boy = Boy("张三", true);
25 boy.eat()
26 boy.sleep()
27 boy.character()
28}
可以看到,我们通过abstract去优化了这个抽象类,而Boy类具体去实现
多态我们可以看抽象类的延伸,即同种功能不同的实现
1abstract class People(var name: String, var sex: Boolean) {
2 abstract fun character()
3}
4
5class Boy(name: String, sex: Boolean) : People(name, sex) {
6 override fun character() {
7 println("$name ${if (sex) "调皮" else "乖巧"}")
8 }
9}
10
11class Girl(name: String, sex: Boolean) : People(name, sex) {
12 override fun character() {
13 println("$name ${if (sex) "调皮" else "乖巧"}")
14 }
15
16}
17
18fun main(args: Array) {
19 val boy = Boy("张三", true);
20 boy.character()
21
22 val girl = Girl("李四", false);
23 girl.character()
24}
比如一个People的性格,男人和女人实现的内容是不一样的。
接口使用的关键字是interface,比如我模拟一下点击事件
1fun main(args: Array) {
2
3 //点击事件
4 click(object :OnClickListener{
5 override fun onClick(position: Int) {
6 println("position:$position")
7 }
8 })
9}
10
11fun click(listener: OnClickListener){
12 listener.onClick(2)
13}
14
15interface OnClickListener {
16 fun onClick(position: Int)
17}
委托和代理是相辅相成的,打个比方,政府招商的一块地,一千万委托给了开发商A,这个时候就是政府委托开发商,而开发商代理了政府,那么这个时候开发商A已一百万的价格委托给了开发商B,从中牟利了九百万,也就导致了现在的豆腐渣工程了,那么我们用代码来实现吧。
1fun main(args: Array) {
2
3 val build = developersA("一千万")
4 build.buildHome()
5}
6
7class developersB(var price: String) : IBuildHome {
8 override fun buildHome() {
9 println("建造房子花费$price")
10 }
11}
12
13class developersA(var price: String) : IBuildHome by developersB("一百万")
14
15//建造方式的能力
16interface IBuildHome {
17 fun buildHome()
18}
可以看到我们是通过by关键字进行委托的,developersA 获得一千万后给developersB一百万依旧把房子建造出来了。
我们在讲kotlin基础的时候实际上是有讲函数式编程的,但是那些都比较粗糙了,这一章节我们讲细一点
我们需要先理解闭包的概念,因为后面讲高阶函数的时候
1.闭包指的是函数的运行环境
2.闭包可以持有函数的运行环境
3.函数内部可以定义函数
4.函数内部也可以定义类
5.在函数中返回一个函数,被返回的函数可以调用主函数的属性
这样可能理解有点抽象,事实上,作为kotlin的特性之一,闭包的用途还是很广泛的,全局变量,顾名思义,其作用域是当前文件甚至文件外的所有地方;而局部变量,我们只能再其有限的作用域里获取。
那么,如何在外部调用局部变量呢?答案就是——闭包,与此给闭包下个定义:闭包就是能够读取其他函数内部变量的函数
1fun main(args: Array) {
2 val add = add()
3 add()
4 add()
5 add()
6 //输出 1 2 3
7}
8
9fun add(): () -> Unit {
10 var count = 0
11 return fun() {
12 println(++count)
13 }
14}
这段代码中可以看到输出的是 1 2 3 ,也就是在return的fun中引用了外部的count
forEach是高阶函数之一,我们先来看下他的用法
1fun main(args: Array) {
2 val lists = listOf(1,2,3,4,5,6)
3 lists.forEach {
4 println(it)
5 }
6}
直接就能打印lists的参数,而要想了解他的工作原理,我们最好来看下他的源码
1/**
2 * Performs the given [action] on each element.
3 */
[email protected]
5public inline fun Iterable.forEach(action: (T) -> Unit): Unit {
6 for (element in this) action(element)
7}
可以看到他是Iterable的扩展函数,接收的是一个action参数,T就是list的类型,也就是
带有Int类型无返回值的函数
而内部则是通过for遍历传入下标得到具体值
还有一个forEachIndexed则是求下标和具体值的,这个自己理解一下
apply函数扩展了所有的泛型对象,在闭包范围内可以任意调用该对象的任意方法,并在最后返回该对象.
我们来看一段示例代码
1class People {
2 val name: String = "小明"
3 val age: Int = 18
4}
5
6fun main(args: Array) {
7 //方式一
8 val p1 = People();
9 println("p1 ${p1.name} ${p1.age} ")
10
11 //方式二
12 People().apply {
13 println("p1 $name $age")
14 }
15}
在闭包内我们可以随意获取和变化参数并且返回对象本身,我们来看下他的源码
[email protected]
2public inline fun T.apply(block: T.() -> Unit): T {
3 contract {
4 callsInPlace(block, InvocationKind.EXACTLY_ONCE)
5 }
6 block()
7 return this
8}
这段代码中T表示对象本身,并且内部又一个契约contract,用于上下文推断。
求最大值
1fun main(args: Array) {
2 val lists = listOf(11,2,30,49,50,6)
3 val max = lists.maxBy { it }
4 println(max)
5}
我们可以传递一个类型进去,他则返回一个类型的最大值,来看下源码
1public inline fun > Iterable.maxBy(selector: (T) -> R): T? {
2 val iterator = iterator()
3 if (!iterator.hasNext()) return null
4 var maxElem = iterator.next()
5 if (!iterator.hasNext()) return maxElem
6 var maxValue = selector(maxElem)
7 do {
8 val e = iterator.next()
9 val v = selector(e)
10 if (maxValue < v) {
11 maxElem = e
12 maxValue = v
13 }
14 } while (iterator.hasNext())
15 return maxElem
16}
这段源码实际上就是在遍历获得最大值,通过R返回回去,当然,还有minBy求最小值
过滤函数,要啥RxJava,要啥自行车,kotlin美滋滋
1fun main(args: Array) {
2 val lists = listOf(11, 2, 30, 49, 50, 6)
3 //求大于20且小于50的值
4 val filter = lists.filter { (it > 20) and (it < 50)}
5 println(filter)
6}
这里可以多条件判断,只要符合规范即可。
看源码得知最终调用的是filterTo
map作为数据转换,根据规则创建新的集合
1fun main(args: Array) {
2 val lists = listOf(11, 2, 30, 49, 50, 6)
3 val filter = lists.map {
4 it.toFloat()
5 }
6 println(filter)
7}
这里我对值进行了toFloat的操作,则全部类型变换为float.
看源码得知最终调用的是mapTo,但是可以知道是创建了一个ArrayList进行的返回
any更多的是作为一个判断函数使用
1fun main(args: Array) {
2 val lists = listOf(11, 2, 30, 49, 50, 6)
3 //集合中是否有30这个值
4 val filter = lists.any {
5 it == 30
6 }
7 println(filter)
8}
来看下源码
1public inline fun Iterable.any(predicate: (T) -> Boolean): Boolean {
2 if (this is Collection && isEmpty()) return false
3 for (element in this) if (predicate(element)) return true
4 return false
5}
传递的参数是一个T的集合类型返回的是一个布尔的函数,可以看到他的实现也是通过for循环遍历的
count作为满足条件的统计函数,我们可以这样使用
1fun main(args: Array) {
2 val lists = listOf(56, 77, 30, 49, 100, 63)
3 //统计及格的同学人数
4 val filter = lists.count {
5 it >= 60
6 }
7 println(filter)
8}
从源码中也可以看出是通过for循环去遍历统计size的
查找函数,只返回满足条件的第一个
1fun main(args: Array) {
2 val lists = listOf(56, 77, 30, 49, 100, 63)
3 //查找第一个及格的同学
4 val filter = lists.find {
5 it >= 60
6 }
7 println(filter)
8}
看源码得知最终调用的是firstOrNull
这是分组函数,用于一个特定条件的分组
1fun main(args: Array) {
2 val lists = listOf(56, 77, 30, 49, 100, 63)
3 //及格和不及格的分组
4 val groupBy = lists.groupBy {
5 it >= 60
6 }
7 println(groupBy)
8}
比如这个示例,我的条件是大于等60则一组,那么其余为一组,可得及格和不及格的分组信息
let的作用主要用于避免非空的判断和作用域替换
1name?.let{
2 //只有不为空才会走进来
3}
DSL领域特定语言,即好玩又好吃的语法糖。
Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法。在Kotlin源码中,有大量的扩展函数来扩展java,这样使得Kotlin比java更方便使用,效率更高。
我们来举个例子:
1fun main(args: Array) {
2 val lists = listOf(56, 77, 30, 49, 100, 63)
3 println(lists.getIndex())
4}
5//扩展:获取第一个下标
6fun List.getIndex():Int{
7 return get(0)
8}
我现在想获取List的第一个值,但是如果List并没有这个方法,我则可以直接给予他扩展。
像我们的and or in 都是中缀表达式,实际上就是省略了点的操作
1fun main(args: Array) {
2 Boy().love(Girl())
3 Girl() love Boy()
4}
5
6class Boy{
7 fun love(girl: Girl){
8 //普通写法
9 }
10}
11
12class Girl{
13 infix fun love(boy: Boy){
14 //中缀表达式
15 }
16}
可以看到,是相当的有趣。
关于Kotlin开发Android应用的时候的一些区别
创建项目的时候,在Configure your peoject 这一项的时候选择kotlin作为开发语言即可
图片大体的东西都不会变化,只是增加了对Kotlin的支持,包括在project/build.gradle中增加了kotlin的插件
图片以及在app/build.gradle中增加了kotlin的stdlib和core库,当然还包括了kotlin的扩展库
图片kt是不需要自己手动findviewbyid的,我们可以用个扩展插件来完成,先来看一段代码
1import kotlinx.android.synthetic.main.activity_main.*
2
3class MainActivity : AppCompatActivity() {
4
5 override fun onCreate(savedInstanceState: Bundle?) {
6 super.onCreate(savedInstanceState)
7 setContentView(R.layout.activity_main)
8
9 mButton.text = "Button"
10 mButton.setOnClickListener {
11 Log.i("TAG", "Button Click")
12 }
13 }
14}
在这段代码中,我们引入了一段包名
1import kotlinx.android.synthetic.main.activity_main.*
我们就可以直接省略初始化的操作,使用id就可以了
这个功能就是kotlin扩展库的功能了
1//Java
2startActivity(new Intent(this,FirstActivity.class))
3//Kotlin
4startActivity(Intent(this,FirstActivity::class.java))
跳转主要还是在class类上,我们可以看到Kotlin需要::class.java
1mButton.setOnClickListener(object :View.OnClickListener{
2 override fun onClick(v: View?) {
3 //Anything
4 }
5})
6//可简化
7mButton.setOnClickListener {
8
9}
这是一个优秀的kotlin辅助库,也可以称之为工具库:
https://github.com/Kotlin/anko
可以使用它的一些语法糖来实现代码写布局,虽然我觉得这个使用率肯定是不高的
1verticalLayout {
2 gravity = Gravity.CENTER
3 val name = editText()
4 val password = editText()
5 name.hint = "请输入账号"
6 password.hint = "请输入密码"
7 button("登录") {
8 onClick {
9 toast("登录成功")
10 }
11 }
12}
我们来运行看一下
intent跳转也相对的简化了
1//需要设置FLAG
2startActivity(intentFor("id" to 5).singleTop())
3//需要传递参数
4startActivity("id" to 5)
5//仅适用跳转
6startActivity()
还有log的优化,以及toast,dialog等,大家自己去发现吧。
这里我准备写一个Kotlin版本的天气预报来给各位演示下一些Koltin语法的基本使用,先来看下效果图
图片功能也很简单,共四个页面
1.启动页,缩放动画
2.城市选择页,自定义View实现城市选择,RecyclerView与城市选择View双向绑定和联动
3.主页,天气详情,RecyclerView显示五天天气
4.设置页,重新选择城市等
使用的接口是聚合数据
使用的网络请求库是retrofit2,好了,我们开始吧;
启动页就一个动画
图片这里使用的是ViewCompat自带的动画,追其根源的话最终还是属性动画实现,监听动画结束之后就可以判断是否选择过城市了,选择过就直接进入主页,没有的话就进入城市选择页,这里作为持久化保存,我是通过Kotlin的单例封装的一个SharedPreferences类
图片城市选择页比较麻烦,我们先来定义请求网络的接口
图片然后就是一层简单的封装了
图片这里首先是对类进行了object的单例化,然后对retrofit2进行了延迟初始化,当我们加载城市列表成功之后,我们就将数据设置给我们的侧边城市选择View,这里我贴一下核心的代码
图片首先就是绘制了,将34个城市名称按照View的高度进行平均分配和绘制,然后紧接着就是滑动的处理了
图片这样,我们就可以很轻松的实现列表的滑动了,但是这里又有一个问题了,那就是联动的问题
先看选择View控制列表的滑动
图片我这里的做法就是根据他的滑动坐标点,找到对应的城市名,然后根据城市名去过滤城市列表以此来找到index,这样就可以滚动列表了
这个是反向的操作,我们需要监听列表的滚动
图片得到第一个可见的下标后以此来推敲出城市名,然后去根据城市名得到index,让View去刷新即可
主页的操作其实就是按部就班的将请求的数据显示出来
图片设置页也是一样的,这里多了个粘贴板的操作罢了
图片最后我们来预览下Gif
图片Kotlin之美还有很多,一文很难讲完,但是循序渐进,还是可以看到效果的,这篇文章的初衷还是希望带领大家走进这门语言,虽然Google强调Kotlin First ,但是就目前而言还只是应用App还没设计到Android 源码层的改动,所以还是百花齐放的阶段,留给大家的时间还是有的,希望大家再接再厉吧。
Github地址:
https://github.com/LiuGuiLinAndroid/Kotlin
推荐我的慕课网Android实战课程,助你暴力提升Android技术。
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固
https://coding.imooc.com/class/390.html
点击阅读原文直达课程详情
点击阅读原文查看更多内容