首先看一段之前的代码:
class MainActivity : AppCompatActivity(), View.OnClickListener {
private val msgList = ArrayList()
private var adapter:MsgAdapter ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMsg()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
adapter = MsgAdapter(msgList)
recyclerView.adapter = adapter
send.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v) {
send -> {
val content = inputText.text.toString()
if (content.isNotEmpty()) {
val msg = Msg(content, Msg.TYPE_SENT)
msgList.add(msg)
adapter?.notifyItemInserted(msgList.size - 1)
recyclerView.scrollToPosition(msgList.size - 1)
inputText.setText("")
}
}
}
}
private fun initMsg() {
val msg1 = Msg("Hello,guy:", Msg.TYPE_RECEIVED)
msgList.add(msg1)
val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
msgList.add(msg2)
val msg3 = Msg("This is Tom. Nice to meet you.", Msg.TYPE_RECEIVED)
msgList.add(msg3)
}
}
上面的变量中,adapter为全局变量,但是其初始化工作是在onCreate方法中进行的,因此不得不先在adapter赋值为null,同时将其类型声明为MsgAdapter?。
而虽然在onCreate方法中对adapter进行初始化,同时能确保onClick方法必然在onCreate方法之后才会调用,但是在onClick方法中调用adapter的任何方法时仍然要进行判空处理,否则编译不能通过。
而对全局变量进行延迟初始化可以解决该问题。
延迟初始化使用lateinit关键字,其会通知Kotlin编译器在晚些时候对该变量进行初始化,这样就不用在一开始就将之赋值null了。
而采用延迟初始化的写法,上面代码可以修改为:
class MainActivity : AppCompatActivity(), View.OnClickListener {
private val msgList = ArrayList()
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMsg()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
adapter = MsgAdapter(msgList)
recyclerView.adapter = adapter
send.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v) {
send -> {
val content = inputText.text.toString()
if (content.isNotEmpty()) {
val msg = Msg(content, Msg.TYPE_SENT)
msgList.add(msg)
adapter.notifyItemInserted(msgList.size - 1)
recyclerView.scrollToPosition(msgList.size - 1)
inputText.setText("")
}
}
}
}
private fun initMsg() {
val msg1 = Msg("Hello,guy:", Msg.TYPE_RECEIVED)
msgList.add(msg1)
val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
msgList.add(msg2)
val msg3 = Msg("This is Tom. Nice to meet you.", Msg.TYPE_RECEIVED)
msgList.add(msg3)
}
}
在上面的代码中,adapter变量前多了lateinit关键字,这样就不用在一开始的时候为变量,同时类型声明也就可以改为MsgAdapter了。而由于MsgAdapter是不可为空的类型,因此用户不需要再onClick方法中进行判空处理,直接调用adapter的相关方法即可。
而同时需要注意的是,如果用户lateinit声明的变量没有初始化的情况下就直接使用,那么程序就会直接崩溃。即在对全局变量使用了lateinit关键字时,要确保在调用之前完成了初始化,否则程序运行状况是未知的。
而用户还可以通过代码来判断一个全局变量是否已经完成了初始化,此时在某些时候就能够有效避免重复对某个变量进行初始化操作:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMsg()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
if (!::adapter.isInitialized) {
adapter = MsgAdapter(msgList)
}
recyclerView.adapter = adapter
send.setOnClickListener(this)
}
上边的语法中,::adapter.isInitialized可用于判断adapter变量是否已经初始化。虽然这种写法比较奇怪,但确实如此。
interface Result
class Success(val msg: String):Result
class Failure(val error:Exception):Result
上面的代码中定义了Result接口,然后定义了两个类实现Result接口。
然后定义了getResultMsg方法,用于获取最终执行结果的信息:
fun getResultMsg(result:Result) = when (result) {
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
getResultMsg方法中接收一个Result参数,然后是一个条件语句,只是最后还需要else部分进行判定,否则Kotlin编译器会认为语句不完整,而无法编译通过。也就是说else部分实际上是多余的。
并且,如果后来又有一个类实现了Result,那么在该条件语句下就会直接报错,这肯定是不可以的。
而使用Kotlin的密封类可以解决这个问题。
密封类的关键字是sealed class,其用法也很简单:
sealed class Result
class Success(val msg: String):Result()
class Failure(val error:Exception):Result()
上面的代码并没有多大变化,只是在继承时Result后多了(),这是因为密封类是一个可继承的类。
而此时getResultMsg方法就可以修改为:
fun getResultMsg(result:Result) = when (result) {
is Success -> result.msg
is Failure -> result.error.message
}
这表示当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求将每一个子类所对应的条件全部处理。这样可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的情况。而如果新增Reult的实现类,上面的方法便会报错,此时必须要多增加该实现类的分支。
而需要注意的是,密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其它类中,这是被密封类底层的实现机制所限制的。
由于密封类通常可以结合RecyclerView适配器中的ViewHolder一起使用。因此考虑下边的代码:
class MsgAdapter(val msgList: List):
RecyclerView.Adapter(){
inner class LeftViewHolder(view:View):RecyclerView.ViewHolder(view) {
val leftMsg: TextView = view.findViewById(R.id.leftMsg)
}
inner class RightViewHolder(view:View):RecyclerView.ViewHolder(view) {
val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED) {
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
LeftViewHolder(view)
} else {
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
RightViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val msg = msgList[position]
when (holder) {
is LeftViewHolder -> holder.leftMsg.text = msg.content
is RightViewHolder -> holder.rightMsg.text = msg.content
else -> throw IllegalArgumentException()
}
}
override fun getItemCount() = msgList.size
}
上面的代码中,MsgViewHolder的onBindViewHolder方法中就存在一个没用到的else部分,因此可以将上面的代码修改为:
sealed class MsgViewHolder(view: View):RecyclerView.ViewHolder(view)
class LeftViewHolder(view:View):MsgViewHolder(view) {
val leftMsg: TextView = view.findViewById(R.id.leftMsg)
}
class RightViewHolder(view:View):MsgViewHolder(view) {
val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}
首先便是定义密封类,使之继承自RecyclerView.ViewHolder,然后使LeftViewHolder和RightViewHolder继承自MsgViewHolder,然后便是:
class MsgAdapter(val msgList: List):
RecyclerView.Adapter(){
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED) {
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
LeftViewHolder(view)
} else {
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
RightViewHolder(view)
}
override fun onBindViewHolder(holder: MsgViewHolder, position: Int) {
val msg = msgList[position]
when (holder) {
is LeftViewHolder -> holder.leftMsg.text = msg.content
is RightViewHolder -> holder.rightMsg.text = msg.content
}
}
override fun getItemCount() = msgList.size
}
上面的代码只是在MsgAdapter使RecyclerView.Adapter的泛型为MsgViewHolder,然后在onBindViewHolder方法中修改参数类型和else部分语句,也就和之前的形式一致了。