《Kotlin从零到精通Android开发》欧阳燊

1,3,5,7,9 -> “张飞”

in 13…19 -> “刘备”

!in 6…10 -> “许褚”

else -> “默认”

}




### []( )类型判断



Java中判断变量是否是String类型  

if(a instanceof String)  

Kotlin 中则使用  

if(a is String)代替  

when/else也支持类型判断



tv_answer.text = when (countType) {

is Long -> “是Long类型”

is Double -> “是浮点类型”

else -> “默认”

}




[]( )3.2循环处理

---------------------------------------------------------------------



### []( )遍历循环



for (int i=0; i<array.length; i++) 方式被替代为:for (i in array.indices) indices表示下标数组



### []( )条件循环



// 左闭右开区间,合法值包括11不包括66  

for (i in 11 until 66) { … }  

// 默认递增1这里默认递增4  

for (i in 23…89 step 4) { … }  

// for循环默认递增downTo代表递减  

for (i in 50 downTo 7) { … }



可是这些方法并不完美,业务需求是千变万化的,并非几种固定模式可以解决,所以while循环和Java处理是一致的



btn_repeat_begin.setOnClickListener {

var poem:String=“”

var i:Int = 0

while (i < poemArray.size) {

if (i%2 ==0) {

poem = “ p o e m poem poem{poemArray[i]}?\n”

} else {

poem = “ p o e m poem poem{poemArray[i]}?\n”

}

i++

}

poem = “ p o e m ? ? ? ? ? ? {poem}?????? poem??????{i}??”

tv_poem_content.text = poem

}




### []( )跳出多重循环



while+continue+break  

通过@标记直接跳出多重循环



btn_repeat_break.setOnClickListener {

var i:Int = 0

var is_found = false

//给外层循环加个outside的标记

outside@ while (i < poemArray.size) {

var j:Int = 0

var item = poemArray[i];

while ( j < item.length) {

if (item[j] == ‘?’) {

is_found = true

//直接跳出outside循环

break@outside

}

j++

}

// 如果内层循环直接跳出两层循环,那么下面的判断就不需要了

// if (is_found)

// break

i++

}

tv_poem_content.text = if (is_found) “找到了” else “没找到”}




总结:  

对于循环,Kotlin仍然保留for和while两种循环,主要区别是Kotlin取消了for(int i =0;i++;i<10)的规则,同时新增了跳出多重循环的支持:break@标记位



[]( )3.3空安全

--------------------------------------------------------------------



### []( )字符串的有效性判断



Kotlin校验字符串空值的几个方法:  

isNullOrEmpty : 为空指针或者字串长度为0时返回true,非空串与可空串均可调用。



isNullOrBlank : 为空指针或者字串长度为0或者全为空格时返回true,非空串与可空串均可调用。



isEmpty : 字串长度为0时返回true,只有非空串可调用。



isBlank : 字串长度为0或者全为空格时返回true,只有非空串可调用。



isNotEmpty : 字串长度大于0时返回true,只有非空串可调用。



isNotBlank : 字串长度大于0且不是全空格串时返回true,只有非空串可调用。



### []( )声明可空变量



Kotlin中默认声明的变量默认都是非空,可空则需要加?  

非空和可空字符串  

var strNotNull:String = “”  

var strCanNull:String?



strNotNull允许调用全部6个方法  

strCanNull只允许调用isNullOrEmpty和isNullOrBlank两个方法  

所以Kotlin对可空串进行了编译检查,一旦发现可空串调用了非空方法,会提示语法错误



下面获取字符串的长度的判断



val strA:String = “非空”  

val strB:String? = null  

val strC:String? = “可空串”  

strA可以直接调用:strA.length,对于strB和strC,必须进行非空判断,否则编译不通过



### []( )校验空值的运算符



?: 类似Java中的三元运算符  

!!表示强行把该变量从可空类型转为非空类型,从而避免变量是否非空的校验,!!强行放弃了非空判断,开发者就得自己判断了。//!!表示 不做非空判断,如果变量为空,就是抛出异常,只有确保为非空时才能使用!!



btn_exclamation_two.setOnClickListener {

try {

length = strB!!.length

tv_check_result.text = “$length”

} catch(e:Exception) {

tv_check_result.text = “空指针异常”

}

}




### []( )总结



Kotlin引入了空安全的概念,并在编译时开展对象是否为空的校验。相关的操作符说明概括如下:



1、声明对象实例时,在类型名称后面加问号,表示该对象可以为空;



2、调用对象方法时,在实例名称后面加问号,表示一旦实例为空就返回null;



3、新引入运算符“?:”,一旦实例为空就返回该运算符右边的表达式;



4、新引入运算符“!!”,通知编译器不做非空校验,运行时一旦发现实例为空就扔出异常;



[]( )3.4等式判断

---------------------------------------------------------------------



### []( )结构相等



Kotlin把字符串当做跟整型一样的基本数据类型,统一运算符  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTRkMDhhNThiY2YxZDRjNWYucG5n?x-oss-process=image/format,png)  

这种不比较存储地址,只比较变量结构内部值的行为,Kotlin称之为结构相等



### []( )引用相等



有时候结构相等并不是真的相等,判断相等需要一种由内而外的全部相等判断,该准则叫引用相等,意思是除了值相等,引用的地址也必须相等,=和!  

引用相等校验的是变量的唯一性,结构相等校验的是变量的等值性



### []( )s和in



除了判断是否相等,还可以判断是否为某种类型,数组是否存在某个元素  

运算符is和!is代替instansof  

运算符in和!in ,变量名in数组名,判断该数组中是否存在此变量



[]( )3.5小结

-------------------------------------------------------------------



本章可以学到  

1.Kotlin的简单分支和多路分支  

2.Kotlin的遍历循环和条件循环  

3.可空变量,可空变量的处理  

4.结构相等和引用相等,类型判断,数组存在判断  

  

  

  

  



[]( )第四章 函数运用

----------------------------------------------------------------------



[]( )4.1 函数的基本用法

-------------------------------------------------------------------------



kotlin默认函数就是public,省略了public  

空安全机制,如果变量允许为空,需要在变量类型后面加?



Kotlin中的函数和变量的声明写法类似



var i:Int

fun main():Int




功能定义var对fun,参数类型Int对Int,唯一的区别就是函数定义多了()以及()内部的入参,Kotlin设计师的初衷正是想把函数作为一个特殊的变量



Java使用void表示不存在返回参数,Kotlin的返回参数一定存在,即使不返回,也会默认返回一个Unit对象  

//Unit类型表示没有返回参数,也可以直接省略Unit声明



fun getDinnerUnit():Unit {

tv_process.text = “张飞”

tv_result.text = “”

}




[]( )4.2 输入参数的变化

-------------------------------------------------------------------------



### []( )入参的默认值



fun getFourBigDefault(general:String, first:String=" 第一", second:String=" 第二 "):String {

var answer:String = “ g e n e r a l : general: generalfirst,$second”

return answer

}




### []( )命名参数



修改默认值



getFourBigDefault(“第一个”) 只修改第一个参数的值

getFourBigDefault(second=“第二个”)只修改第二个参数的值




使用了命名参数表达式:second=“第二个”,该表达式实现了给指定参数赋值的功能。



### []( )可变参数



Java中的可变参数是String… strs,在Kotlin中新增了vararg关键字代替



fun getFourBigVararg(general:String, first:String=" 第一", second:String=" 第二", vararg otherArray: String?):String {

var answer:String = “ g e n e r a l : general: generalfirst,$second”

//循环取出可变参数数组

for (item in otherArray) {

answer = “ a n s w e r , answer, answeritem”

}

return answer

}




可变数组也是可以的 :vararg otherArray: Array



[]( )4.3几种特殊函数

-----------------------------------------------------------------------



### []( )泛型函数



定义泛型函数时,要在函数名称前面添加 ,入参也肯定包含

fun appendString(tag:String, vararg otherInfo: T?):String {

var str:String = “$tag:”

for (item in otherInfo) {

str = “ s t r str str{item.toString()},”

}

return str

}

调用

appendString(“小于10的素数”,2,3,5,7)

appendString(“花钱的日子”,5.20,6.18,11.11,12.12)




### []( )内联函数



//该函数既不接收Array也不接收Array

fun setArrayNumber(array:Array) {

var str:String = “数组排列”

for (item in array) {

str = str + item.toString() + ", "

}

tv_function_result.text = str

}

//只有内联函数才可以被具体化

inline fun setArrayStr(array:Array) {

var str:String = “数组排列”

for (item in array) {

str = str + item.toString() + ", "

}

tv_function_result.text = str

}

var int_array:Array = arrayOf(1, 2, 3)

var float_array:Array = arrayOf(1.0f, 2.0f, 3.0f)

var double_array:Array = arrayOf(11.11, 22.22, 33.33)

//Kotlin要求参数类型完全匹配,所以Int继承Number也不能调用 setArrayNumber传送Int类型

//btn_generic_number.setOnClickListener { setArrayNumber(int_array) }

btn_generic_number.setOnClickListener {

when (count%3) {

0 -> setArrayStr(int_array)

1 -> setArrayStr(float_array)

else -> setArrayStr(double_array)

}

count++

}




### []( )简化函数



函数其实是一种特殊的变量,既然是变量那么就可以用=赋值



fun factorial(n:Int):Int {

if (n <= 1) n

else n*factorial(n-1)

}

可以简写为:

fun factorial(n:Int):Int = if (n <= 1) n else n*factorial(n-1)




### []( )尾递归函数tailrec



如果函数尾部递归调用自身,加上tailrec关键字表示是一个尾递归函数,此时编译器会优化递归,用循环代替递归,从而避免栈溢出的情况。



tailrec fun findFixPoint(x: Double = 1.0): Double

= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))




### []( )高阶函数



A函数作为B函数的入参,那么B函数称为高阶函数,A函数称为函数变量



fun maxCustom(array: Array, greater: (T, T) -> Boolean): T? {

var max: T? = null

for (item in array)

if (max == null || greater(item, max))

max = item

return max

}

greater就是函数变量




调用



因为高阶函数maxCustom是泛型函数,所以要在函数名称后面加:

var string_array:Array = arrayOf(“How”, “do”, “you”, “do”)

maxCustom(string_array, { a, b -> a.length > b.length })




第二个参数被大括号包了起来,这是Lambda表达式的匿名函数写法,中间的->把匿名函数分为2部分,前半部分表示输入函数,后半部分表示函数体。{ a, b -> a.length > b.length }按照规范的写法是下面这样的,



fun anonymous(a:String, b:String):Boolean {

var result:Boolean = a.length > b.length

return result

}




[]( )4.4 增强系统函数

------------------------------------------------------------------------



### []( )扩展函数



开发者给系统类补写新的方法,比如扩展系统的Array类,可以在你定义的函数名称前加上Array,Array



拓展函数结合泛型函数

fun Array.swap(pos1: Int, pos2: Int) {

val tmp = this[pos1] //this表示数组变量自身

this[pos1] = this[pos2]

this[pos2] = tmp

}




有了拓展函数,数组变量可以直接调用,像是系统自带的方法一样



### []( )拓展高阶函数



maxCustom是求数组元素的最大值,可以把该方法拓展到Array类中,数组变量就可以直接调用了



fun Array.maxCustomize(greater: (T, T) -> Boolean): T? {

var max: T? = null

for (item in this)

if (max == null || greater(item, max))

max = item

return max

}




### []( )日期时间函数



除了数组,日期和时间的函数还是很常见的,一般我们都封装一个工具类,在Kotlin中我们拓展一下Date类



//只返回日期字符串

fun Date.getNowDate(): String {

val sdf = SimpleDateFormat(“yyyy-MM-dd”)

return sdf.format(this)

}

//只返回时间字符串

fun Date.getNowTime(): String {

val sdf = SimpleDateFormat(“HH:mm:ss”)

return sdf.format(this)

}

//返回详细时间字符串,精确到毫秒

fun Date.getNowTimeDetail(): String {

val sdf = SimpleDateFormat(“HH:mm:ss.SSS”)

return sdf.format(this)

}




调用 :



Date().getNowDate()




### []( )单例对象



虽然拓展函数已经实现日期的获取,但是稍显繁琐,比如:Date().getNowDate(),一共占了4个括号。而且这些方法是依赖系统类来拓展的,如果没有系统类,那么也就无法拓展了。所以这个时候,Java中的Uitls工具类反而更灵活,更广泛,那么Kotlin有没有工具类的写法呢?



Java中一般采用static方法作为工具类,表示静态,无需对类进行构造即可直接调用。  

既然是工具,那么一旦制定了规格就不会改变了,不能构造也不能修改,所以Kotlin使用object修饰,称为单例对象,相当于Java中的工具类。



**我们用单例对象改造后**:



object DateUtil {

//返回日期

val nowDate: String

get() {

val sdf = SimpleDateFormat(“yyyy-MM-dd”)

return sdf.format(Date())

}

//返回时间

val nowTime: String

get() {

val sdf = SimpleDateFormat(“HH:mm:ss”)

return sdf.format(Date())

}

//返回具体时间

val nowTimeDetail: String

get() {

val sdf = SimpleDateFormat(“HH:mm:ss.SSS”)

return sdf.format(Date())

}

}




调用:



DateUtil.nowDateTime




[]( )4.5小结

-------------------------------------------------------------------



1.定义输入和输出的完整函数  

2.输入参数的几种特殊定义,包括默认参数,命名参数,可变参数,以及调用  

3.特殊函数的使用,包括泛型函数,单例函数,简化函数,尾递归函数,高阶函数  

4.合理利用拓展函数,单例对象对系统函数进行功能增强  

  

  

  

  



[]( )第五章 类和对象

======================================================================



[]( )5.1类的构造

---------------------------------------------------------------------



### []( )类的简单定义



Kotlin省略了pubic,默认就是public  

:代替extends  

Kotlin进行继承时,后面多了()  

Kotlin对类初始化的方法是init,Java则为构造函数  

Kotlin创建实例省略new



### []( )类的构造函数



Kotlin把函数看成特殊变量,那么类就能看成特殊函数,所以构造方法可以直接加在类名后面,而init方法是实例化该类的初始化动作。  

Kotlin有主构造函数和二级构造函数的概念,来满足多个构造函数的情况



class AnimalMain constructor(context:Context, name:String) {

init {

context.toast(“$name”)

}

constructor(context:Context, name:String, sex:Int) : this(context,

name) {

var sexName:String = if(sex==0) “?” else “?”

context.toast(“?? n a m e ? {name}? name?{sexName}?”)

}

}




二级构造函数没有函数名称,只用关键字constructor表示  

二级构造函数需要调用主构造函数



但是测试中,发现会调用2次toast,能否不强制调用主构造函数呢?  

Kotlin设定了主构造函数不是必须的,可以去掉主构造函数,这样两个构造函数是互相独立的,就不会去调用主构造函数了



class AnimalSeparate {

constructor(context:Context, name:String) {

context.toast(“这是$name”)

}

constructor(context: Context, name:String, sex:Int) {

var sexName:String = if(sex==0) “公” else “母”

context.toast(“ n a m e ? {name}? name?{sexName}”)

}

}




### []( )带默认参数的构造参数



如此折腾一番,貌似跟Java一样了,没有简化,别急,Kotlin还有大招  

可以把它们合并成一个带默认参数的主构造函数,既能输入2个参数,也能输入3个参数



//类的主构造函数使用了默认参数

class AnimalDefault (context: Context, name:String, sex:Int = 0) {

init {

var sexName:String = if(sex==0) “?” else “?”

context.toast(“?? n a m e ? {name}? name?{sexName}?”)

}

}




但是这个默认参数只支持Kotlin代码调用,若想让Java也识别默认参数,得往该类的构造函数添加注解@JvmOverloads,告知编译器这个类是给Java重载用的



//加上@JvmOverloads的目的是让Java代码也能识别默认参数

//因为添加了注解,所以必须加上constructor

class AnimalDefault @JvmOverloads constructor(context: Context, name:St

ring, sex:Int = 0) {

init {

var sexName:String = if(sex==0) “?” else “?”

context.toast(“?? n a m e ? {name}? name?{sexName}?”)

}

}




现在Java也能像Kotlin一样调用多构造方法了。  

总结:  

主构造函数的入参在类名后面声明,函数体则位于init方法中,二级构造函数必定先调用主构造函数,Kotlin构造函数也支持默认参数,避免了冗余的构造函数定义



[]( )5.2类的成员

---------------------------------------------------------------------



### []( )成员属性



Java方式保存入参的值,太啰嗦了



class WildAnimal (name:String, sex:Int = 0) {

var name:String

val sex:Int

init {

this.name = name

this.sex = sex

}

}




可以简化为:



class WildAnimal (var name:String, val sex:Int = 0) {

}




只有2个改动之处:  

增加关键字var,表示声明与该参数同名的可变属性并自动赋值  

增加关键字val,表示声明与该参数同名的不可变属性并自动赋值  

外部调用,就可以直接调用name:



var animal = WildAnimal(“小狗”, “公”)

${animal.name}




所以Kotlin精简了太多:  

1.冗余的同名属性声明语句  

2.冗余的同名属性赋值语句  

3.冗余的属性的set和get方法  

如果某个字段并非入参的同名属性,就需要在内部显式的声明



class WildAnimalMember (var name:String, val sex:Int = 0) {

//非空的属性必须在声明是赋值或者在构造函数中赋值(本例是在构造函数中)

//否则编译器会报“Property must be initialized or be abstract”

var sexName:String

init {

sexName = if(sex==0) “?” else “?”

}

}




外部当然可以这样访问了:${animal.sexName}



### []( )成员方法



class WildAnimalFunction (var name:String, val sex:Int = 0) {

var sexName:String

init {

sexName = if(sex==0) “公” else “母”

}

//成员方法和普通方法一样

fun getDesc(tag:String):String {

return “${sexName}”

}

}




调用:



var animal = WildAnimalFunction(animalName, animalSex)

animal.getDesc(“动物园”)




### []( )伴生对象



Java中有个static静态方法,Kotlin去掉了static,无法直接声明静态成员,用伴生对象代替



class WildAnimalCompanion (var name:String, val sex:Int = 0) {

var sexName:String

init {

sexName = if(sex==0) “公” else “母”

}

fun getDesc(tag:String):String {

return “{sexName}”

}

//在类加载时就运行伴生对象的代码块,其作用相当于Java里面的static { … }静态代码块

?

//关键字companion表示伴随,object表示对象,WildAnimal表示伴生对象的名称

companion object WildAnimal{

fun judgeSex(sexName:String):Int {

var sex:Int = when (sexName) {

“公”,“雄” -> 0

“母”,“雌” -> 1

else -> -1

}

return sex

}

}

}




外部调用:



WildAnimalCompanion.WildAnimal.judgeSex(“公”)

和简化的

WildAnimalCompanion.judgeSex(“公”) (更像Java中的static调用)




### []( )静态属性



class WildAnimalConstant(var name:String, val sex:Int = MALE) {

var sexName:String

init {

sexName = if(sex==MALE) “?” else “?”

}

fun getDesc(tag:String):String {

return “??? t a g ? ? ? tag??? tag???{name}?${sexName}??”

}

companion object WildAnimal{

//静态常量的值是不可变的,所以要使用关键字val修饰

val MALE = 0

val FEMALE = 1

val UNKNOWN = -1

fun judgeSex(sexName:String):Int {

var sex:Int = when (sexName) {

“公”,“雄” -> MALE

“母”,“雌” -> FEMALE

else -> UNKNOWN

}

return sex

}

}

}




Kotlin 的类成员分为实例成员和静态成员两种  

实例成员包括成员属性和成员方法,入参同名的成员属性可以在构造函数中直接声明,外部调用必须通过类的实例才能访问成员属性和成员方法。  

静态成员包括静态属性和静态方法,在类的伴生对象中定义,外部可以通过类名直接访问静态成员。



[]( )5.3类的继承

---------------------------------------------------------------------



### []( )开放性修饰符



Kotlin默认每个类都不能被继承,如果要让某个类成为基类,就需把该类开放出来,也就是添加关键字open作为修饰符  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTdjZjI0MjcxY2MzMmM1ZDIucG5n?x-oss-process=image/format,png)



### []( )普通类的继承



open class Bird (var name:String, val sex:Int = MALE) {

//变量,方法,类默认都是public,一般把public省略了

//public var sexName:String

var sexName:String

init {

sexName = getSexName(sex)

}

//open和private不能共存

//open private fun getSexName(sex:Int):String {

open protected fun getSexName(sex:Int):String {

return if(sex==MALE) “?” else “?”

}

fun getDesc(tag:String):String {

return “ n a m e , {name}, name{sexName}”

}

companion object BirdStatic{

val MALE = 0

val FEMALE = 1

val UNKNOWN = -1

fun judgeSex(sexName:String):Int {

var sex:Int = when (sexName) {

“?”,“?” -> MALE

“?”,“?” -> FEMALE

else -> UNKNOWN

}

return sex

}

}

}




下面我们来继承看看



//父类Bird已经在构造函数中声明了属性,所以Duck不用重复声明

//即子类的构造函数在入参签名无需增加val和var

class Duck(name:String=“??”, sex:Int = Bird.MALE) : Bird(name, sex) {

}




子类也可以定义新的成员属性和成员方法,或者重写父类的open方法



### []( )抽象类



//子类的构造函数,原来的入参不用加var和val,新增的入参必须加var或val

//因为抽象类不能直接使用,所以构造函数不必给默认参数赋值

abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(n

ame, sex) {

val numberArray:Array = arrayOf(“?”,“?”,“?”,“?”,“?”,“?”

,“?”,“?”,“?”,“?”);

//抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型的

open??

//open abstract fun callOut(times:Int):String

abstract fun callOut(times:Int):String

}




下面创建个实体类继承 抽象方法:



class Cock(name:String=" 鸡 “, sex:Int = Bird.MALE, voice:String=” 嘎嘎嘎") : Chicken(name, sex, voice) {

override fun callOut(times: Int): String {

var count = when {

//when判断大于小于时,要把完整的判断条件写到每个分支中

times<=0 -> 0

times>=10 -> 9

else -> times

}

return “ s e x N a m e sexName sexNamename v o i c e , {voice}, voice{numberArray[count]}”

}

}




### []( )接口



Kotlin和Java一样也只能继承一个类,所以只能通过接口定义几个抽象方法,然后在实现类中重写,从而间接实现C++中的多重继承功能。  

注意点:  

1.接口不能定义构造函数  

2.接口的内部方法要被实现类重写,这些方法默认为抽象方法  

3.于Java不同的是,Kotlin允许接口内实现非抽象方法,而Java接口中必须全部是抽象方法



interface Behavior {

//接口内部默认是抽象open方法,所以可以省略abstract和open

open abstract fun fly():String

//比如这个方法,就省略了

fun swim():String

//kotlin接口内部允许实现方法,该方法不是抽象方法,不过依然是open类型,接口内部默认open

fun run():String {

return “跑步”

}

//Kotlin接口允许声明抽象属性,实现该接口的方法必须重载该属性

//和方法一样,open和abstract也可以省略

//open abstract var skilledSports:String

var skilledSports:String

}




我们创建一个实现类来实现接口:



class Goose(name:String=“鹅”, sex:Int = Bird.MALE) : Bird(name, sex), B

ehavior {

override fun fly():String {

return “起飞”

}

override fun swim():String {

return “额鹅鹅鹅”

}

//这个run方法可实现,可不实现

override fun run():String {

//super用来调用父类的属性和方法,由于Kotlin的接口支持实现方法,所以super所指的对象也就是父类的interface

return super.run()

}

//重载了来之接口的抽象属性

override var skilledSports:String = “??”

}




### []( )接口代理



class BehaviorFly : Behavior {

override fun fly():String {

return “飞1”

}

override fun swim():String {

return “游泳1”

}

override fun run():String {

return “跑1”

}

override var skilledSports:String = “飞翔”

}

class BehaviorSwim : Behavior {

override fun fly():String {

return “飞2”

}

override fun swim():String {

return “游泳2”

}

override fun run():String {

return “跑2”

}

override var skilledSports:String = “游泳”

}

class BehaviorRun : Behavior {

override fun fly():String {

return “飞3”

}

override fun swim():String {

return “游泳3”

}

override fun run():String {

return “跑3”

}

override var skilledSports:String = “奔跑”

}




下面定义一个代理类:



//只有接口才能使用关键字by进行代理操作

class WildFowl(name:String, sex:Int=MALE, behavior:Behavior) : Bird(nam

e, sex), Behavior by behavior {

}




外部调用:代理类的入参要传入具体的行为对象



WildFowl(“老鹰”, Bird.MALE, BehaviorFly())

WildFowl(“企鹅”, behavior=BehaviorSwim())

WildFowl(“鸵鸟”, Bird.MALE, BehaviorRun())




总结:  

1.Kotlin的类默认不可被继承,必须添加open才可以,而Java默认允许被继承,除了final类  

2.Kotlin新增了修饰符internal,表示只对本模块(app自身)开放  

3.:代替Java中的extends和implement  

4.Kotlin接口中可以实现方法,而Java接口中全部必须是抽象方法  

5.Kotlin引入接口代理的概念,而Java中不存在代理的写法



[]( )5.4几种特殊类

----------------------------------------------------------------------



### []( )嵌套类



Java中的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员



class Tree(var treeName:String) {

// 内部类即嵌套类

class Flower (var flowerName:String) {

fun getName():String {

return “$flowerName”

//普通嵌套类不能访问外部成员

//return “ t r e e N a m e , {treeName}, treeName{flowerName}”

 }

}

}




调用嵌套类:



//调用嵌套类时,只能引用外部类的类名,不能调用外部类的构造函数

val peachBlossom = Tree.Flower(“桃花”);

peachBlossom.getName()




### []( )内部类



普通嵌套类不能访问外部类的成员,那么如果想访问怎么办呢?  

inner关键字,加载嵌套类class的前面即可,就可以访问外部类的成员了



class Tree(var treeName:String) {

// 加上inner 关键字就变成了内部类

inner class Flower (var flowerName:String) {

fun getName():String {

return “ t r e e N a m e , {treeName}, treeName{flowerName}”

}

}

}




调用内部类:



//调用内部类时,必须调用外部类的构造函数

val peach = Tree(“桃树”).Flower(“桃花”);

tv_class_secret.text = peach.getName()




总结:  

内部类比嵌套类多了inner关键字  

内部类可以访问外部类的成员  

调用内部类必须调用外部类的构造函数,调用嵌套类只能调用外部类的类名



### []( )枚举类



Java中的枚举类:



enum Season {

SPRING, SUMMER, AUTUMN, WINTER

}




Kotlin中的枚举类:



enum class SeasonType {

SPRING, SUMMER, AUTUMN, WINTER

}

enum class SeasonName (val seasonName:String) {

SPRING(“春天”),

SUMMER(“夏天”),

AUTUMN(“秋天”),

WINTER(“冬天”)

}




调用:



btn_class_enum.setOnClickListener {

if (count%2 == 0) {

//ordinal表示枚举类型的序号,name表示枚举类型的名称

tv_class_secret.text = when (count++%4) {

SeasonType.SPRING.ordinal -> SeasonType.SPRING.name

SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name

SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name

SeasonType.WINTER.ordinal -> SeasonType.WINTER.name

else -> “未知”

}

} else {

tv_class_secret.text = when (count++%4) {

//使用自定义属性seasonName表示

SeasonName.SPRING.ordinal -

SeasonName.SPRING.seasonName

SeasonName.SUMMER.ordinal -

SeasonName.SUMMER.seasonName

SeasonName.AUTUMN.ordinal -

SeasonName.AUTUMN.seasonName

SeasonName.WINTER.ordinal -

SeasonName.WINTER.seasonName

else -> “未知”

枚举类的构造函数是给枚举类型使用的,外部不能直接调用枚举类的构造函数

//else -> SeasonName(“??”).name

}

}

}




### []( )密封类



当when语句判断枚举类的时候,末尾例行公事加了else分支,因为when语句不知道有4种枚举,因此以防万一,必须要有else分支。为了解决判断多余分支的问题,Kotlin提出了"密封类"的概念,密封类像是一种更严格的枚举类,它内部有且仅有自身的实例对象,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱。



sealed class SeasonSealed {

//密封类内部的每个嵌套类都必须继承该类

class Spring (var name:String) : SeasonSealed()

class Summer (var name:String) : SeasonSealed()

class Autumn (var name:String) : SeasonSealed()

class Winter (var name:String) : SeasonSealed()

}




有了密封类,外部使用when就不需要else分支了  

调用:



btn_class_sealed.setOnClickListener {

var season = when (count++%4) {

0 -> SeasonSealed.Spring(“春天”)

1 -> SeasonSealed.Summer(“夏天”)

2 -> SeasonSealed.Autumn(“秋天”)

else -> SeasonSealed.Winter(“冬天”)

}

//密封类是一种严格的枚举类,它的值是一个有限的集合

//密封类确保条件分支覆盖了所有的枚举类型,因此不再需要else分支

tv_class_secret.text = when (season) {

is SeasonSealed.Spring -> season.name

is SeasonSealed.Summer -> season.name

is SeasonSealed.Autumn -> season.name

is SeasonSealed.Winter -> season.name

}

}




### []( )数据类



即Java中的Bean实体类,Java中的做法:  

1.定义字段,构造函数  

2.定义get /set方法  

3.只想修改对象的某几个字段的值,其他字段也必须修改  

4.调试时,得把每个字段的值都打印出来  

这些任务都毫无技术含量可言,所以Kotlin有了数据类



只需要在class前面增加data关键字,并声明完整参数的构造函数,即可实现以下功能:  

1.自动声明于构造函数入参同名的属性字段  

2.自动实现每个属性字段的get/set方法  

3.自动提供equals方法,用于比较两个数据对象是否相等  

4.自动提供copy方法,允许完整负责数据对象,也可在复制后单独修改某几个字段的值  

5.自动提供toString方法,便于打印数据对象中保存的值



下面我们马上定义一个:



//数据类必须有主构造函数,且至少有一个输入参数

//并且要声明与输入参数同名的属性,即输入参数前面添加var或val

//数据类不能是基类,也不能是子类,不能是抽象类,也不能是内部类,更不能是密封类

data class Man(var name:String, var age:Int, var sex:String) {

}




精简的前提是要有规范:  

1.数据类必须有主构造函数,且至少有一个输入参数,因为它的属性字段要和输入参数一一对应,如果没有属性字段,那么也就不是数据类了  

2.输入参数前面添加var或val,这保证每个入参都会自动声明同名的属性字段  

3.只能是独立的类,不能是其他类型的类,否则不同规则之间会产生冲突



var man = Man(“张飞”, “25”, “男”)

//数据类的copy方法不带参数,表示复制一模一样的对象

var man2 = man.copy()

//数据类的copy方法带参数,表示指定参数另外赋值

var man3 = man.copy(name=“诸葛亮”)

//数据类自带equals方法和toString方法

man.equals(man2)

man.toString()




copy,equals,toString方法都是数据类自带的,提高了开发者的编码效率



### []( )模板类(泛型类)



常见的ArrayList,HashMap,AsyncTask都是模板类  

举个例子:  

计算小河的长度,如果输入数字就以m为单位,如果输入汉字就以米为单位



//在类名后面添加,表示这个是一个模板类

class River (var name:String, var length:T) {

fun getInfo():String {

var unit:String = when (length) {

is String -> “米”

//Int,Long,Float,Double都是数字类型的Number

is Number -> “m”

else -> “”

}

return “ n a m e 的长度是 {name}的长度是 name的长度是length$unit”

}

}




调用的时候,要在类名后面补充<参数类型>,从而动态指定实际的参数类型。  

正如声明变量那样,编译器根据初始值判断变量类型,就不用显式指定类型  

模板类也有这种偷懒写法,编译器根据入参的值就能知晓参数类型,那么调用的时候,就不用显式指定<参数类型>了



btn_class_generic.setOnClickListener {

var river = when (count++%4) {

//模板类声明对象时,要在模板类的类名后面加上<参数类型>

0 -> River(“小溪”, 100)

//如果编译器根据入参能判断参数类型,那么可以省略

1 -> River(“小溪”, 99.9f)

//当然保守起见,还是按照规矩添加<参数类型>

2 -> River(“??”, 50.5)

//如果你已经是老手了,怎么方便怎么来,Kotlin的涉及初衷就是偷懒

else -> River(“大河”, “一千”)

}

tv_class_secret.text = river.getInfo()

}




[]( )5.5小结

-------------------------------------------------------------------



1.类的定义和主构造函数和二级构造函数  

2.类内部定义的成员属性和成员方法,伴生对象的静态属性和静态方法  

3.修饰符,继承抽象类,接口,接口代理  

4.特殊类:嵌套类,内部类,枚举类,密封类,数据类,模板类



附加:  

[Kotlin系列之let、with、run、apply、also函数的使用]( )



[]( )第六章 Kotlin使用简单控件

==============================================================================



[]( )6.1使用按钮控件

-----------------------------------------------------------------------



### []( )按钮button



按钮事件三种Kotlin编码方式:匿名函数,内部类,接口实现



1.匿名函数方式



btn_click_anonymos.setOnClickListener { v ->

//Kotlin变量类型转换使用as

toast(“${(v as Button).text}”)

}

btn_click_anonymos.setOnLongClickListener { v ->

//Kotlin变量类型转换使用as

longToast(“${(v as Button).text}”)

true

}




2.内部类方式



private inner class MyClickListener : View.OnClickListener {

override fun onClick(v: View) {

toast(“${(v as Button).text}”)

}

}

private inner class MyLongClickListener : View.OnLongClickListener {

override fun onLongClick(v: View): Boolean {

longToast(“${(v as Button).text}”)

return true

}

}




调用的时候:



btn_click_inner.setOnClickListener(MyClickListener())

btn_click_inner.setOnLongClickListener(MyLongClickListener())




3.接口实现方式  

内部类的话,每个事件都要定义一个内部类,多了也不好,试试接口实现



class ButtonClickActivity : AppCompatActivity(), OnClickListener, OnLon

gClickListener {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_button_click)

btn_click_interface.setOnClickListener(this)

btn_click_interface.setOnLongClickListener(this)

}

override fun onClick(v: View) {

if (v.id == R.id.btn_click_interface) {

toast(“${(v as Button).text}”)

}

}

override fun onLongClick(v: View): Boolean {

if (v.id == R.id.btn_click_interface) {

longToast(“${(v as Button).text}”)

}

return true

}

}




### []( )复选框CheckBox



![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWZjMzA4NmQ1MjU1NTZkM2YucG5n?x-oss-process=image/format,png)



### []( )单选按钮RadioButton



同Java



[]( )6.2使用页面布局

-----------------------------------------------------------------------



### []( )线性布局LinearLayout



1.  Kotlin允许对属性orientation直接赋值,从而取代了setOrientation方法;类似的还有属性gravity取代了setGravity方法;

2.  Kotlin使用关键字as进行变量的类型转换;

3.  Kolin支持调用dip方法将dip数值转换为px数值,倘若由Java编码则需开发者自己实现一个像素转换的工具类;  

    因为dip方法来自于Kotlin扩展的Anko库,所以需要在Activity代码头部加上下面一行导入语句:  

    import org.jetbrains.anko.dip  

    ![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTAzNmJhZDFjNGU3MGFiMmMucG5n?x-oss-process=image/format,png)



### []( )相对布局 RelativeLayout



rl\_params.addRule和rl\_params.above  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTIyYzcyMDg2OWM1ZjhkODAucG5n?x-oss-process=image/format,png)



### []( )约束布局 ConstraintLayout



layout\_constraintTop\_toTopOf : 该控件的顶部与另一个控件的顶部对齐  

layout\_constraintTop\_toBottompOf : 该控件的顶部与另一个控件的底部对齐  

layout\_constraintBottom\_toTopOf : 该控件的底部与另一个控件的顶部对齐  

layout\_constraintBottom\_toBottomOf : 该控件的底部与另一个控件的底部对齐  

layout\_constraintLeft\_toLeftOf : 该控件的左侧与另一个控件的左侧对齐  

layout\_constraintLeft\_toRightOf : 该控件的左侧与另一个控件的右侧对齐  

layout\_constraintRight\_toLeftOf : 该控件的右侧与另一个控件的左侧对齐  

layout\_constraintRight\_toRightOf : 该控件的右侧与另一个控件的右侧对齐



若要利用代码给约束布局动态添加控件,则可照常调用addView方法,不同之处在于,新控件的布局参数必须使用约束布局的布局参数,即ConstraintLayout.LayoutParams,该参数通过setMargins/setMarginStart/setMarginEnd方法设置新控件与周围控件的间距,至于新控件与周围控件的位置约束关系,则可参照ConstraintLayout.LayoutParams的下列属性说明:  

topToTop : 当前控件的顶部与指定ID的控件顶部对齐  

topToBottom : 当前控件的顶部与指定ID的控件底部对齐  

bottomToTop : 当前控件的底部与指定ID的控件顶部对齐  

bottomToBottom : 当前控件的底部与指定ID的控件底部对齐  

startToStart : 当前控件的左侧与指定ID的控件左侧对齐  

startToEnd : 当前控件的左侧与指定ID的控件右侧对齐  

endToStart : 当前控件的右侧与指定ID的控件左侧对齐  

endToEnd : 当前控件的右侧与指定ID的控件右侧对齐



[]( )6.3使用图文控件

-----------------------------------------------------------------------



###文本视图TextView  

ellipsize+TextUtils.TruncateAt.MARQUEE 跑马灯效果  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTUwMGM3OGVhYjVmNGM0MjAucG5n?x-oss-process=image/format,png)  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTE4YzBlYTRmZWM4Nzk5MDQucG5n?x-oss-process=image/format,png)



Java

tv_marquee.setGravity(Gravity.LEFT | Gravity.CENTER);

Kotlin

tv_marquee.gravity = Gravity.LEFT or Gravity.CENTER




![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWQzOTNlMDUyMTY5MDZmYmUucG5n?x-oss-process=image/format,png)



### []( )图像视图 ImageView



![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWE2YThhYWU2OTYzYTA5ZGEucG5n?x-oss-process=image/format,png)



### []( )文本编辑框EditText



![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWM5MDAyNDViOTYwMzdmYzUucG5n?x-oss-process=image/format,png)



[]( )6.4Activity活动跳转

-----------------------------------------------------------------------------



### []( )传送配对字段数据



Java:

Intent intent = new Intent(MainActivity.this, LinearLayoutActivity.class);

startActivity(intent);

Kotlin anko

startActivity()

带参数的

Java:

Intent intent = new Intent(this, ActSecondActivity.class);

intent.putExtra(“request_time”, DateUtil.getNowTime());

intent.putExtra(“request_content”, et_request.getText().toString());

startActivity(intent);

Kotlin

方式一:用to关键字

startActivity(

“request_time” to DateUtil.nowTime,

“request_content” to et_request.text.toString())

方式二:Pair

startActivity(

Pair(“request_time”, DateUtil.nowTime),

Pair(“request_content”, et_request.text.toString()))




接收方:



val bundle = intent.extras

val request_time = bundle.getString(“request_time”)

val request_content = bundle.getString(“request_content”)




### []( )传送序列化数据



//@Parcelize注解表示自动实现Parcelize接口的相关方法

@Parcelize

data class MessageInfo(val content: String, val send_time: String) : Pa

rcelable {

}

//@Parcelize标记需要在build.gradle设置experimental = true

androidExtensions {

experimental = true

}

//传送序列化数据

val request = MessageInfo(et_request.text.toString(), DateUtil.nowTime)

startActivity(“message” to request)




接收方:



//获得Parcelable的参数

val request = intent.extras.getParcelable(“message”)

${request.content}




### []( )跳转时指定启动模式



anko库取消了intent方法有利有弊,弊端是intent对象的setAction,setData,addCategory,setFlags怎么设置  

那么下面这种方式就可以拿到intent对象设置了



val intent = intentFor(

“request_time” to DateUtil.nowTime,

“request_content” to et_request.text.toString())




Android有两种方式设置启动模式:清单文件和代码设置



#### []( )清单文件设置



![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTcyNmQ1ZjllZTRiNTZhMjAucG5n?x-oss-process=image/format,png)



#### []( )代码设置



![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWMyZmQwZTUzNjY0ZGI1OWEucG5n?x-oss-process=image/format,png)



anko库仍然简化了代码:startActivity(intent.newTask())  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTM2NDlmNzdkNTJlMWQzNWYucG5n?x-oss-process=image/format,png)



### []( )处理返回数据 startActivityForResult



跳转



val info = MessageInfo(et_request.text.toString(), DateUtil.nowTime)

//ForResult表示需要返回参数

startActivityForResult(0, “message” to info)




下个页面返回数据



btn_act_response.setOnClickListener {

val response = MessageInfo(et_response.text.toString(), DateUtil.no

wTime)

val intent = Intent()

intent.putExtra(“message”, response)

//调用setResult表示参数返回到上个页面

setResult(Activity.RESULT_OK, intent)

finish()

}




收到数据后:



//返回本页面时回调onActivityResult

override fun onActivityResult(requestCode: Int, resultCode: Int, data:

Intent?) {

if (data != null) {

//获得应答参数

val response = data.extras.getParcelable

(“message”)

tv_request.text = " r e s p o n s e . s e n d t i m e , {response.send_time}, response.sendtime{response.content}"

}

}




[]( )6.5实战项目

---------------------------------------------------------------------



略



[]( )6.6总结

-------------------------------------------------------------------



1.按钮控件  

2.布局视图  

3.图文控件  

4.activity跳转  

5.anko库对话框



[]( )第七章 Kotlin操纵复杂控件

==============================================================================



[]( )7.1使用视图排列

-----------------------------------------------------------------------



### []( )下拉框spinner



private fun initSpinner() {

val starAdapter = ArrayAdapter(this, R.layout.item_select, starArra

y)

starAdapter.setDropDownViewResource(R.layout.item_dropdown)

//Android 8.0之后的findViewById要求在后面添加""才能进行类型转换

val sp = findViewById(R.id.sp_dialog) as Spinner

sp.prompt = “请选择行星”

sp.adapter = starAdapter

sp.setSelection(0)

sp.onItemSelectedListener = MySelectedListener()

}

private val starArray = arrayOf(“水星”, “水星”, “火星”, “木星”)

internal inner class MySelectedListener : OnItemSelectedListener {

override fun onItemSelected(arg0: AdapterView<*>, arg1: View, arg2:

Int, arg3: Long) {

toast(“${starArray[arg2]}”)

}

override fun onNothingSelected(arg0: AdapterView<*>) {}

}




anko库的简化:



<TextView

android:id=“@+id/tv_spinner”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:layout_toRightOf=“@+id/tv_dialog”

android:gravity=“center”

android:drawableRight=“@drawable/arrow_down”

android:textColor=“@color/black”

android:textSize=“17sp” />

val satellites = listOf(“水星”, “水星”, “火星”, “木星”)

tv_spinner.text = satellites[0]

tv_spinner.setOnClickListener {

selector(“请选择行星”, satellites) { i ->

tv_spinner.text = satellites[i]

toast(“${tv_spinner.text}”)

}

}




主要借助了:import org.jetbrains.anko.selector  

anko库里面的selector源码是利用了AlertDialog的setItem方法  

###列表视图listview  

Kotlin的扩展视图selector  

Kotlin要求每个变量都要初始化  

lateinit延迟初始化属性,那么修饰的变量无需赋空值,使用的时候也不用加!!



class PlanetListAdapter(private val context: Context, private val planetList: MutableList, private val background: Int) : BaseAdapter() {

override fun getCount(): Int = planetList.size



override fun getItem(position: Int): Any = planetList[position]



override fun getItemId(position: Int): Long = position.toLong()



override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

    var view = convertView

    val holder: ViewHolder

    if (convertView == null) {

        view = LayoutInflater.from(context).inflate(R.layout.item_list_view, null)

        holder = ViewHolder()

        //先声明视图持有者的实例,再依次获取内部的各个控件对象

        holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayout

        holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView

        holder.tv_name = view.findViewById(R.id.tv_name) as TextView

        holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView

        view.tag = holder

    } else {

        holder = view.tag as ViewHolder

    }

    val planet = planetList[position]

    holder.ll_item.setBackgroundColor(background)

    holder.iv_icon.setImageResource(planet.image)

    holder.tv_name.text = planet.name

    holder.tv_desc.text = planet.desc

    return view!!

}



//ViewHolder中的属性使用关键字lateinit延迟初始化

inner class ViewHolder {

    lateinit var ll_item: LinearLayout

    lateinit var iv_icon: ImageView

    lateinit var tv_name: TextView

    lateinit var tv_desc: TextView

}

}




以上的Kotlin代码总算有点模样了,虽然总体代码还不够精简,但是至少清晰明了,其中主要运用了Kotlin的以下三项技术:



1、构造函数和初始化参数放在类定义的首行,无需单独构造,也无需手工初始化;  

2、像getCount、getItem、getItemId这三个函数,仅仅返回简单运算的数值,可以直接用等号取代大括号;  

3、对于视图持有者的内部控件,在变量名称前面添加lateinit,表示该属性为延迟初始化属性;



### []( )网格视图



在前面的列表视图一小节中,给出了Kotlin改写后的适配器类,通过关键字lateinit固然避免了麻烦的空校验,可是控件对象迟早要初始化的呀,晚赋值不如早赋值。翻到前面PlanetListAdapter的实现代码,认真观察发现控件对象的获取其实依赖于布局文件的视图对象view,既然如此,不妨把该视图对象作为ViewHolder的构造参数传过去,使得视图持有者在构造之时便能一块初始化内部控件。据此改写后的Kotlin适配器代码如下所示:



class PlanetGridAdapter(private val context: Context, private val planetList: MutableList, private val background: Int) : BaseAdapter() {

override fun getCount(): Int = planetList.size



override fun getItem(position: Int): Any = planetList[position]



override fun getItemId(position: Int): Long = position.toLong()



override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

    var view = convertView

    val holder: ViewHolder

    if (view == null) {

        view = LayoutInflater.from(context).inflate(R.layout.item_grid_view, null)

        holder = ViewHolder(view)

        //视图持有者的内部控件对象已经在构造时一并初始化了,故这里无需再做赋值

        view.tag = holder

    } else {

        holder = view.tag as ViewHolder

    }

    val planet = planetList[position]

    holder.ll_item.setBackgroundColor(background)

    holder.iv_icon.setImageResource(planet.image)

    holder.tv_name.text = planet.name

    holder.tv_desc.text = planet.desc

    return view!!

}



//ViewHolder中的属性在构造时初始化

inner class ViewHolder(val view: View) {

    val ll_item: LinearLayout = view.findViewById(R.id.ll_item) as LinearLayout

    val iv_icon: ImageView = view.findViewById(R.id.iv_icon) as ImageView

    val tv_name: TextView = view.findViewById(R.id.tv_name) as TextView

    val tv_desc: TextView = view.findViewById(R.id.tv_desc) as TextView

}

}




外部调用



gv_planet.adapter = PlanetGridAdapter(this, Planet.defaultList, Color.W

HITE)




### []( )循环视图RecyclerView



RecyclerView可以实现线性列表,网格列表,瀑布流网格  

![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTIyYmFiZDQ2YjQ2NTY4MzYucG5n?x-oss-process=image/format,png)  

布局管理器  

LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager  

循环适配器  

循环适配器和其他适配器的区别  

1.自带视图持有者ViewHolder及其重用功能,无需开发者手工重用ViewHolder  

2.未带点击和长按功能,需要开发者自己实现  

3.增加区分不同列表项的视图类型  

4.可单独对个别项进行增删改操作,无须刷新整个列表  

Kotlin实现:



//ViewHolder在构造时初始化布局中的控件对象

class RecyclerLinearAdapter(private val context: Context, private val infos: MutableList) : RecyclerView.Adapter(), OnItemClickListener, OnItemLongClickListener {

val inflater: LayoutInflater = LayoutInflater.from(context)



//获得列表项的数目

override fun getItemCount(): Int = infos.size



//创建整个布局的视图持有者

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

    val view: View = inflater.inflate(R.layout.item_recycler_linear, parent, false)

    return ItemHolder(view)

}



//绑定每项的视图持有者

override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    val vh: ItemHolder = holder as ItemHolder

    vh.iv_pic.setImageResource(infos[position].pic_id)

    vh.tv_title.text = infos[position].title

    vh.tv_desc.text = infos[position].desc

    // 列表项的点击事件需要自己实现

    vh.ll_item.setOnClickListener { v ->

        itemClickListener?.onItemClick(v, position)

    }

    vh.ll_item.setOnLongClickListener { v ->

        itemLongClickListener?.onItemLongClick(v, position)

        true

    }

}



//ItemHolder中的属性在构造时初始化

inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {

    var ll_item = view.findViewById(R.id.ll_item) as LinearLayout

    var iv_pic = view.findViewById(R.id.iv_pic) as ImageView

    var tv_title = view.findViewById(R.id.tv_title) as TextView

    var tv_desc = view.findViewById(R.id.tv_desc) as TextView

}



private var itemClickListener: OnItemClickListener? = null

fun setOnItemClickListener(listener: OnItemClickListener) {

    this.itemClickListener = listener

}



private var itemLongClickListener: OnItemLongClickListener? = null

fun setOnItemLongClickListener(listener: OnItemLongClickListener) {

    this.itemLongClickListener = listener

}



override fun onItemClick(view: View, position: Int) {

    val desc = "您点击了第${position+1}项,标题是${infos[position].title}"

    context.toast(desc)

}



override fun onItemLongClick(view: View, position: Int) {

    val desc = "您长按了第${position+1}项,标题是${infos[position].title}"

    context.toast(desc)

}

}




可是这个循环适配器RecyclerLinearAdapter仍然体量庞大,细细观察发现其实它有着数个与具体业务无关的属性与方法,譬如上下文对象context、布局载入对象inflater、点击监听器itemClickListener、长按监听器itemLongClickListener等等,故而完全可以把这些通用部分提取到一个基类,然后具体业务再从该基类派生出特定的业务适配器类。根据这种设计思路,提取出了循环视图基础适配器,它的Kotlin代码如下所示:



//循环视图基础适配器

abstract class RecyclerBaseAdapter

}




一旦有了这个基础适配器,实际业务的适配器即可由此派生而来,真正需要开发者编写的代码一下精简了不少。下面便是个循环视图的网格适配器,它实现了类似淘宝主页的网格频道栏目,具体的Kotlin代码如下所示:



//把公共属性和公共方法剥离到基类RecyclerBaseAdapter,

//此处仅需实现getItemCount、onCreateViewHolder、onBindViewHolder三个方法,以及视图持有者的类定义

class RecyclerGridAdapter(context: Context, private val infos: MutableList) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {

override fun getItemCount(): Int = infos.size



override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

    val view: View = inflater.inflate(R.layout.item_recycler_grid, parent, false)

    return ItemHolder(view)

}



override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    val vh = holder as ItemHolder

    vh.iv_pic.setImageResource(infos[position].pic_id)

    vh.tv_title.text = infos[position].title

}



inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {

    var ll_item = view.findViewById(R.id.ll_item) as LinearLayout

    var iv_pic = view.findViewById(R.id.iv_pic) as ImageView

    var tv_title = view.findViewById(R.id.tv_title) as TextView

}

}




然而基类不过是雕虫小技,Java也照样能够运用,所以这根本不入Kotlin的法眼,要想超越Java,还得拥有独门秘笈才行。注意到适配器代码仍然通过findViewById方法获得控件对象,可是号称在Anko库的支持之下,Kotlin早就无需该方法就能直接访问控件对象了呀,为啥这里依旧靠老牛拉破车呢?其中的缘由是Anko库仅仅实现了Activity活动页面的控件自动获取,并未实现适配器内部的自动获取。不过Kotlin早就料到了这一手,为此专门提供了一个插件名叫LayoutContainer,只要开发者让自定义的ViewHolder继承该接口,即可在视图持有者内部无需获取就能使用控件对象了。这下不管是在Activity代码,还是在适配器代码中,均可将控件名称拿来直接调用了。这么神奇的魔法,快来看看Kotlin的适配器代码是如何书写的:



//利用Kotlin的插件LayoutContainer,在适配器中直接使用控件对象,而无需对其进行显式声明

class RecyclerStaggeredAdapter(context: Context, private val infos: MutableList) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {

override fun getItemCount(): Int = infos.size



override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

    val view: View = inflater.inflate(R.layout.item_recycler_staggered, parent, false)

    return ItemHolder(view)

}



override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    (holder as ItemHolder).bind(infos[position])

}



//注意这里要去掉inner,否则运行报错“java.lang.NoSuchMethodError: No virtual method _$_findCachedViewById”

class ItemHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(item: RecyclerInfo) {

        iv_pic.setImageResource(item.pic_id)

        tv_title.text = item.title

    }

}

}




还需要在模块的build.gradle增加:



androidExtensions {

experimental = true

}




上面采用了新的适配器插件,似乎已经大功告成,可是依然要书写单独的适配器代码,仔细研究发现这个RecyclerStaggeredAdapter还有三个要素是随着具体业务而变化的,包括:



1、列表项的布局文件资源编码,如R.layout.item\_recycler\_staggered;  

2、列表项信息的数据结构名称,如RecyclerInfo;  

3、对各种控件对象的设置操作,如ItemHolder类的bind方法;



除了以上三个要素,RecyclerStaggeredAdapter内部的其余代码都是允许复用的,因此,接下来的工作就是想办法把这三个要素抽象为公共类的某种变量。对于第一个的布局编码,可以考虑将其作为一个整型的输入参数;对于第二个的数据结构,可以考虑定义一个模板类,在外部调用时再指定具体的数据类;对于第三个的bind方法,若是Java编码早已束手无策,现用Kotlin编码正好将该方法作为一个函数参数传入。依照三个要素的三种处理对策,进而提炼出来了循环适配器的通用类RecyclerCommonAdapter,详细的Kotlin代码示例如下:



//循环视图通用适配器

//将具体业务中会变化的三类要素抽取出来,作为外部传进来的变量。这三类要素包括:

//布局文件对应的资源编号、列表项的数据结构、各个控件对象的初始化操作

class RecyclerCommonAdapter(context: Context, private val layoutId: Int, private val items: List, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder>(context) {

override fun getItemCount(): Int = items.size



override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

    val view: View = inflater.inflate(layoutId, parent, false)

    return ItemHolder<T>(view, init)

}



override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    val vh: ItemHolder<T> = holder as ItemHolder<T>

    vh.bind(items.get(position))

}



//注意init是个函数形式的输入参数

class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {

    fun bind(item: T) {

        init(view, item)

    }

}

}




有了这个通用适配器,外部使用适配器只需像函数调用那样传入这三种变量就好了,具体调用的Kotlin代码如下所示:



//第二种方式:使用把三类可变要素抽象出来的通用适配器

val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,

    {view, item ->

        val iv_pic = view.findViewById(R.id.iv_pic) as ImageView

        val tv_title = view.findViewById(R.id.tv_title) as TextView

        iv_pic.setImageResource(item.pic_id)

        tv_title.text = item.title

    })

rv_staggered.adapter = adapter




最终出炉的适配器仅有十行代码不到,其中的关键技术——函数参数真是不鸣则已、一鸣惊人。至此本节的适配器实现过程终于落下帷幕,一路上可谓是过五关斩六将,硬生生把数十行的Java代码压缩到不到十行的Kotlin代码,经过不断迭代优化方取得如此彪炳战绩。尤其是最后的两种实现方式,分别运用了Kotlin的多项综合技术,才能集Kotlin精妙语法之大成。



[]( )7.2使用材质设计

-----------------------------------------------------------------------



MaterialDesign库提供了协调布局CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout



### []( )协调布局CoordinatorLayout



继承自ViewGroup  

对齐方式:layout\_gravity,  

子视图位置:app:layout\_anchor?app:layout\_anchorGravity  

行为:app:layout\_behavior



FloatingActionButton 悬浮按钮  

悬浮按钮会悬浮在其他视图之上  

隐藏和显示悬浮按钮时会播放切换动画,hide和show方法  

悬浮按钮默认会随着便签条Snackbar的出现或消失动态调整位置  

###工具栏Toolbar  

Android 5.0之后使用Toolbar代替ActionBar  


# 结尾

- 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升

[外链图片转存中...(img-OECMYwEB-1720105780920)]

- **一线互联网Android面试题含详解(初级到高级专题)**

> 这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

![img](https://img-blog.csdnimg.cn/img_convert/af3742c63df978344d0dd571a573d597.jpeg)

> 有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我


yout.item_recycler_staggered, parent, false)

        return ItemHolder(view)

    }

 

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        (holder as ItemHolder).bind(infos[position])

    }

 

    //注意这里要去掉inner,否则运行报错“java.lang.NoSuchMethodError: No virtual method _$_findCachedViewById”

    class ItemHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {

        fun bind(item: RecyclerInfo) {

            iv_pic.setImageResource(item.pic_id)

            tv_title.text = item.title

        }

    }

}





还需要在模块的build.gradle增加:


androidExtensions {

    experimental = true

}



上面采用了新的适配器插件,似乎已经大功告成,可是依然要书写单独的适配器代码,仔细研究发现这个RecyclerStaggeredAdapter还有三个要素是随着具体业务而变化的,包括:

1、列表项的布局文件资源编码,如R.layout.item_recycler_staggered;

2、列表项信息的数据结构名称,如RecyclerInfo;

3、对各种控件对象的设置操作,如ItemHolder类的bind方法;

除了以上三个要素,RecyclerStaggeredAdapter内部的其余代码都是允许复用的,因此,接下来的工作就是想办法把这三个要素抽象为公共类的某种变量。对于第一个的布局编码,可以考虑将其作为一个整型的输入参数;对于第二个的数据结构,可以考虑定义一个模板类,在外部调用时再指定具体的数据类;对于第三个的bind方法,若是Java编码早已束手无策,现用Kotlin编码正好将该方法作为一个函数参数传入。依照三个要素的三种处理对策,进而提炼出来了循环适配器的通用类RecyclerCommonAdapter,详细的Kotlin代码示例如下:


//循环视图通用适配器

//将具体业务中会变化的三类要素抽取出来,作为外部传进来的变量。这三类要素包括:

//布局文件对应的资源编号、列表项的数据结构、各个控件对象的初始化操作

class RecyclerCommonAdapter<T>(context: Context, private val layoutId: Int, private val items: List<T>, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder<T>>(context) {

 

    override fun getItemCount(): Int = items.size

 

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        val view: View = inflater.inflate(layoutId, parent, false)

        return ItemHolder<T>(view, init)

    }

 

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        val vh: ItemHolder<T> = holder as ItemHolder<T>

        vh.bind(items.get(position))

    }

 

    //注意init是个函数形式的输入参数

    class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {

        fun bind(item: T) {

            init(view, item)

        }

    }

}



有了这个通用适配器,外部使用适配器只需像函数调用那样传入这三种变量就好了,具体调用的Kotlin代码如下所示:


//第二种方式:使用把三类可变要素抽象出来的通用适配器

val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,

        {view, item ->

            val iv_pic = view.findViewById(R.id.iv_pic) as ImageView

            val tv_title = view.findViewById(R.id.tv_title) as TextView

            iv_pic.setImageResource(item.pic_id)

            tv_title.text = item.title

        })

rv_staggered.adapter = adapter



最终出炉的适配器仅有十行代码不到,其中的关键技术——函数参数真是不鸣则已、一鸣惊人。至此本节的适配器实现过程终于落下帷幕,一路上可谓是过五关斩六将,硬生生把数十行的Java代码压缩到不到十行的Kotlin代码,经过不断迭代优化方取得如此彪炳战绩。尤其是最后的两种实现方式,分别运用了Kotlin的多项综合技术,才能集Kotlin精妙语法之大成。

7.2使用材质设计


MaterialDesign库提供了协调布局CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout

协调布局CoordinatorLayout

继承自ViewGroup

对齐方式:layout_gravity,

子视图位置:app:layout_anchor?app:layout_anchorGravity

行为:app:layout_behavior

FloatingActionButton 悬浮按钮

悬浮按钮会悬浮在其他视图之上

隐藏和显示悬浮按钮时会播放切换动画,hide和show方法

悬浮按钮默认会随着便签条Snackbar的出现或消失动态调整位置

###工具栏Toolbar

Android 5.0之后使用Toolbar代替ActionBar

结尾

  • 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升

[外链图片转存中…(img-OECMYwEB-1720105780920)]

  • 一线互联网Android面试题含详解(初级到高级专题)

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

[外链图片转存中…(img-fD36QtuL-1720105780921)]

有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值