Android KotlinのonClickListnerを紐解く【初学者向け】
AndroidのonClickListener
みなさんAndroidで以下のようなコードありますよね。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.main_text)
val button: Button = findViewById(R.id.main_button)
button.setOnClickListener {
textView.text = "ボタンが押された!"
}
}
}
今回、注目するのは、
button.setOnClickListener {
textView.text = "ボタンが押された!"
}
の部分です。
少しずつ紐解いていきます。
何をしているのか
Button
の親クラスであるView
クラスのメソッド、onClickListener()
を呼び出しているのですが、実際には丸括弧 ( )
ではなく波括弧 { }
での呼び出しをしていますね。
知っている人にとっては「そこから解説するのか」と思うかもしれませんが、そうでない人もいると思うので解説します。
これを理解するには、引数とラムダの関係を知る必要があります。
関数の引数とラムダ
メソッド (関数) の引数の中で、最後のものをラムダ式で受け取る場合は、波括弧 { }
で外に出してその中身を記述することができます。
例えば以下のようなコードだと分かりやすいでしょうか。
これから紹介するコードは全て、do something
とコンソール出力されるKotlinコードです。
ラムダのSample その1
/** 何も引数を取らない、かつ何も返さない関数(ラムダ) [listener] を引数にとる */
fun doSomething(listener: ()->Unit) {
listener() // ラムダは関数のように呼び出すことができる
}
// { }で中身をその場で書いて渡す
doSomething {
println("do something")
}
もちろん引数名はlistener
でなくても良いです。
そしてただの関数の引数なので以下のようにも書けます。
/** 何も引数を取らない、かつ何も返さない関数(ラムダ) [listener] を引数にとる */
fun doSomething(listener: ()->Unit) {
listener() // ラムダは関数のように呼び出すことができる
}
// 先にラムダを変数定義して渡す
val listener: ()->Unit = {
println("do something")
}
doSomething(listener)
ラムダのSample その2
もし、ラムダに引数がある場合は以下のようになります。
/** Stringを一つ引数に取り、何も返さない関数(ラムダ) [listener] を引数にとる */
fun doSomething(listener: (String)->Unit) {
listener("do something") // ラムダは関数のように呼び出すことができる
}
// 引数を一つ取るラムダはデフォルトで it が名前で割り当てられる
doSomething {
println(it)
}
// ラムダ内の引数名は自分でも決められる
doSomething { message ->
println(message)
}
ここまでで、基礎的な知識はOKです。
本題に入ります。
onClickListenerの仕組み
onClickListener
はButton
クラスのメソッドではなく、実際には親クラスにあたるView
のメソッドです。
定義を見てみると、
// View.java
public void setOnClickListener(@Nullable View.OnClickListener l) {
// ...
}
となっています。
よく見てみると、引数はラムダではなくView.OnClickListener
という型 (クラス) を求めています。
なぜラムダのように { }
で外側に書けたのでしょうか?
SAM – Single Abstract Method
答えはSAMという機能を使っています。
View.OnClickListener
は定義を見てもわかるように、interface
です。
// View.java
public interface OnClickListener {
void onClick(View var1);
}
ここで、メソッドがひとつしかないinterface
をSAM (Single Abstraction Method) interfaceもしくは Functional interfaceと呼びます。(以下省略してSAMと書きます)
SAMは特別なinterface
で、SAMを引数にとるメソッドはラムダのような記述が可能になります。
実際に簡易的なコードで確認してみましょう。
/** 超シンプルなSAM */
fun interface SAMSample {
fun doSomething()
}
/** SAMを引数に取る関数 */
fun callSample(samSample: SAMSample) {
samSample.doSomething()
}
// 呼び出し側
callSample {
println("do something")
}
KotlinではSAMインターフェースは明示的にfun interface
で定義します。
これは、オブジェクト式を使った以下のようなコードとも同義になります。
callSample(object : SAMSample {
override fun doSomething() {
println("do something")
}
})
この書き方はSAMでは冗長なので基本的にやるメリットはないです。
しかし、以下のような書き方は実装をファイル分けできるので、やる意味はあります。
もしラムダの中が複雑で長くなるようであれば以下の方法を取ることも選択肢に入れましょう。
/** 実際にinterfaceを継承して実装する */
class MySample : SAMSample {
override fun doSomething() {
// ...
println("do something")
// ...
}
}
// 呼び出し側
callSample(MySample())
このやり方は実装部分を再利用可能であるメリットもありますが、基本的にラムダを使ってスマートに書くことが多いです。
余談 : onClickListenerもこう書ける
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.main_text)
val button: Button = findViewById(R.id.main_button)
button.setOnClickListener(MyTextOnClickListener(textView))
}
class MyTextOnClickListener(val textView: TextView) : View.OnClickListener {
override fun onClick(v: View?) {
textView.text = "ボタンが押された!"
}
}
}