拋出問題
看過我的Kotlin-高階函數的使用(二)都知道,我們的setOnClickListener
可以這樣寫:
view.setOnClickListener {
}
但是當接口有多個實現方法的時候我們可能就需要這樣實現了:
edittext.addTextChangedListener(object :TextWatcher{
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
通常我們只需要在onTextChanged
裏執行一些邏輯,其餘的方法用不到,這樣就顯得很不優雅了,那有沒有辦法顯得更加的優雅呢,答案肯定是有噠~~~
解決問題
1.使用擴展函數封裝對應的方法
我們可以使用擴展方法內部實現3個方法,然後讓其中一個方法invoke
出去,實現之後像這樣:
edittext.onTextChanged { s, start, before, count ->
}
實現:
fun EditText.onTextChanged(changed:(s: CharSequence?, start: Int, before: Int, count: Int)->Unit){
addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
changed(s,start,before, count)
}
})
}
這裏我假設你已經理解了擴展函數的使用,如果看不太懂,可以先看看我的這篇文章Kotlin-高階函數的使用(一)
代碼一下子清爽起來了,並且我可以專注於一個方法邏輯的實現,但是有一個缺點,萬一我需要在beforeTextChanged
裏實現一些邏輯呢,萬一我想實現其他的方法呢,有人會說我再申明一個beforeTextChanged
的擴展方法啊,然後像這樣:
edittext.onTextChanged { s, start, before, count ->
}
edittext.beforeTextChanged{s, start, count, after->
}
這樣寫實際上是申明瞭兩個監聽,onTextChanged
是會失效的,所以這種方案pass。我們可以想到在一個方法裏申明多個lambda
,貌似解決了問題,於是就像這樣:
edittext.addTextChangedListener({ s ->
}, { s, start, count, after ->
}, { s, start, before, count->
})
實現:
fun EditText.addTextChangedListener(after:(s: Editable?)->Unit,before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit,
changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit){
addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {
after(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
before(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
changed(s,start,before, count)
}
})
}
我滴媽,這啥跟啥,誰要是這麼寫估計出門會被打。其實這種思路是對的,但是可能敲出來的代碼他有自己的想法.....
2.使用命名參數和默認值解決lambda方法區分問題
我們知道上面那種實現的缺點就是方法不易區分,so,我們只需要加入一個命名參數就解決問題啦:
edittext.addTextChangedListener(
after = { s ->
},
before = { s, start, count, after ->
},
changed = { s, start, before, count ->
}
)
實現:
inline fun EditText.addTextChangedListener(crossinline after:(s: Editable?)->Unit,
crossinline before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit,
crossinline changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit){
addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {
after(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
before(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
changed(s,start,before, count)
}
})
}
使用
crossinline
關鍵字來規定命名參數,但必須申明方法爲inline
,關於內聯,詳見官方文檔
這樣看起來就清晰多了,每個lambda
對應什麼方法一目瞭然,但是,回到我們最初說的,我們有時只需要其中的一個方法的,這樣還是要申明3個方法呀,別急,加個默認值搞定:
實現:
inline fun EditText.addTextChangedListener(crossinline after:(s: Editable?)->Unit={},
crossinline before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit={
_, _, _, _ ->
},
crossinline changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit={
_, _, _, _ ->
}){
addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {
after(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
before(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
changed(s,start,before, count)
}
})
}
使用:
edittext.addTextChangedListener(
changed = { s, start, before, count ->
}
)
這樣看起來就舒服多了,既達到了申明單個多個實現的靈活選擇,又達到了方法名的命名區分,感覺人生已經到達了巔峯有木有!!
我們知道如果方法申明瞭默認值,那麼這個參數可以不申明,我們正是利用了這個特性來達到的效果,關於這個特性也可以用到接口上,當我們有些接口不想讓它每次都實現,可以在接口後面加入一個返回值,如果沒有返回值可以返回一個
Unit
,像這樣:
interface Test {
fun test1() = Unit
fun test2()
}
這樣的話test2
爲必須實現,test1
就變成了選擇實現
總結:
經過addTextChangedListener
的一個例子,我們學習了擴展函數,接口與命名參數的運用,在實際項目中,建議讀者們可以將常用的一些接口實現做一些封裝,具體選擇那種方式視實際情況而定,如果你不經常的使用它,那麼就不用去特意去封裝它,不然會造成方法臃腫。
關於方法與接口的封裝,還有更加高級的用法,就是DSL
,這種方案在Anko
框架中使用的非常頻繁,有興趣的可以去研究一下