一個setOnClickListener的錯誤
寫了個demo,點擊事件離奇的不生效,非常困擾。
開始實在是看不出有什麼問題,錯誤代碼如下:
findViewById<Button>(R.id.btn).setOnClickListener {
View.OnClickListener {
...
}
}
運行不報錯,就是不觸發OnClickListener中的代碼。
那麼正確的寫法應該是怎樣呢?
如下所示:
// 寫法1:OnClickListener寫法
findViewById<Button>(R.id.btn).setOnClickListener(
View.OnClickListener {
...
}
)
或者這樣:
// 寫法2:閉包寫法
findViewById<Button>(R.id.btn).setOnClickListener {
...
}
爲什麼那個錯誤的寫法不報錯但是卻不能正確運行呢?
因爲錯誤的寫法實際上是在寫法2的閉包裏面又聲明瞭一個OnClickListener,僅僅聲明listener當然不能調用裏面的代碼了。
將函數作爲參數傳遞
爲什麼setOnClickListener
有兩種寫法呢?
實際上這裏setOnClickListener
這個方法有兩個重載:
// 參數是OnClickListener對象
fun setOnClickListener(l: View.OnClickListener?)
// 參數是閉包
fun setOnClickListener(l: (v: View) -> Unit)
因爲有兩個重載方法,setOnClickListener
纔可以有兩種寫法。
所謂閉包:
在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。
kotlin中的閉包一般稱爲lambda表達式,詳細內容可以查看:
我們這裏只看將函數作爲參數傳遞的用法。
因爲閉包這一特性的存在,上面setOnClickListener
纔可以不使用傳統的OnClickListener
, 下面我們自己定義一個簡單的例子來說明。
傳統寫法:
// 聲明listener
interface TestListener {
fun test()
}
// 聲明使用listener作爲參數的方法
private fun setListener(listener: TestListener) {
// 調用listener的test方法
listener.test()
}
// 方法中使用
fun main(){
setListener(object : TestListener {
override fun test() {
Log.i("test_tag", "test Listener")
}
})
}
閉包寫法:
// 聲明方法
private fun setListener(listener: () -> Unit) {
// 調用傳入的方法
listener()
}
// 方法中使用
fun main(){
setListener { Log.i("test_tag", "test unit") }
}
明顯簡化了許多,再也不需要通過聲明接口設置監聽了。
需要額外說明將函數作爲參數傳遞使用的幾種變種:
帶參數的閉包:
// 帶參數
private fun setListener(l: (c: Context) -> Unit) {
l(applicationContext)
}
// 使用,只有一個參數時,可以用it代替傳入的參數
setListener { Toast.makeText(it, "test param", Toast.LENGTH_SHORT).show() }
帶參數且有返回值:
private fun setListener(l: (c: Context) -> Boolean) {
val b = l(applicationContext)
Toast.makeText(applicationContext, if (b) "當前時間是偶數" else "當前時間是奇數", Toast.LENGTH_SHORT).show()
}
// 使用,只有一個參數時,可以用it代替傳入的參數
setListener {
Toast.makeText(it, "test param", Toast.LENGTH_SHORT).show()
// 當前時間是否是偶數
System.currentTimeMillis() % 2 == 0L
}