はじめに
Androidのソフト開発ではイベントを受け取るためにリスナーを使います。
ボタンをクリックするとonClickイベントが発生するのでそれをリスナーで受け取って処理できるので無駄がありません。
では自分で作ったクラスに独自のイベントを作成するにはどのようにすれば良いのでしょうか?
ネットや書籍を調べても書かれていません。他の人はどのようにして設計しているのか不思議です。
説明はしますが、何の資料も無いため手探りで適当に作っていたら偶然出来ただけなので詳細な説明は出来ません。むしろ何故動いているのか知りたいぐらいです。
クラスの設計
独自のイベントを使うクラスを設計してみます。
クラスCalcはメソッドexecuteの引数v1とv2の商を計算するクラスとします。
計算が出来ればonSuccess()イベントが発生し、右式に0を渡されると**onDivZero()**イベントを発生させます。
レイアウト設計
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/etV1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberDecimal" />
<EditText
android:id="@+id/etV2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberDecimal" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onButtonClick"
android:text="Button" />
<TextView
android:id="@+id/tvExecute"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
EditText2つ etV1、etV2とonClickにイベント割り当て済みのボタン、TextViewの tvExecuteを配置しています。
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val calc = Calc()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
calc.setListener(mainListener)
}
private val mainListener = object : CalcInterface{
override fun onSuccess(result: Int) {
super.onSuccess(result)
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText(result.toString())
}
override fun onDivZero() {
super.onDivZero()
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText("div zero error!")
}
}
fun onButtonClick(view: View){
val etV1 = findViewById<EditText>(R.id.etV1)
val etV2 = findViewById<EditText>(R.id.etV2)
calc.execute(etV1.text.toString().toInt(),etV2.text.toString().toInt())
}
}
interface CalcInterface : Calc.Listener {
fun onSuccess(result: Int){}
fun onDivZero(){}
}
class Calc() {
private var listener: CalcInterface? = null
interface Listener {}
fun setListener(listener: Calc.Listener?) {
if (listener is CalcInterface) {
this.listener = listener as CalcInterface
}
}
fun execute(v1 : Int,v2 : Int){
if (v2 == 0){
listener?.onDivZero();
}
else{
listener?.onSuccess(v1 / v2);
}
}
}
プログラムの説明
まずはリスナーの説明
interface CalcInterface : Calc.Listener {
fun onSuccess(result: Int){}
fun onDivZero(){}
}
インターフェイスとして CalcInterfaceを定義しています。Calc.Listenerは、この後に定義するCalcに実装されているinterface を参照しています。何故この参照で動くのかは不明です。
ここで作ったメソッドクラス内で呼ぶと、このクラスを生成したMainActivityのリスナーを通じてイベントが発生します。
計算が正常に終わったことを通知するonSuccess(result: Int)と右辺が0で計算できない事を表すonDivZeroを定義しています。
この中では処理が必要では無いので空ですし、この後説明するメイン側で継承したときにsuperで上位クラスを呼ぶ必要もありません。
次にクラス本体の説明です。
class Calc() {
private var listener: CalcInterface? = null
interface Listener {}
fun setListener(listener: Calc.Listener?) {
if (listener is CalcInterface) {
this.listener = listener as CalcInterface
}
}
fun execute(v1 : Int,v2 : Int){
if (v2 == 0){
listener?.onDivZero();
}
else{
listener?.onSuccess(v1 / v2);
}
}
}
private var listener: CalcInterface? = null
interface Listener {}
変数名listenerでCalcInterfaceインターフェイスを定義しています。
クラス内のインターフェイスとしてListener も定義しています。
fun setListener(listener: Calc.Listener?) {
if (listener is CalcInterface) {
this.listener = listener as CalcInterface
}
}
メインとリスナーをつなげるためのメソッドsetListenerです。
fun execute(v1 : Int,v2 : Int){
if (v2 == 0){
listener?.onDivZero();
}
else{
listener?.onSuccess(v1 / v2);
}
}
計算を実行するメソッドexecuteです。
結果によってlistenerが持つメソッドのイベントを発生させます。
作ったクラスをMainActivityに実装します。
class MainActivity : AppCompatActivity() {
private val calc = Calc()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
calc.setListener(mainListener)
}
private val mainListener = object : CalcInterface{
override fun onSuccess(result: Int) {
super.onSuccess(result)
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText(result.toString())
}
override fun onDivZero() {
super.onDivZero()
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText("div zero error!")
}
}
fun onButtonClick(view: View){
val etV1 = findViewById<EditText>(R.id.etV1)
val etV2 = findViewById<EditText>(R.id.etV2)
calc.execute(etV1.text.toString().toInt(),etV2.text.toString().toInt())
}
}
まずは実装から
private val calc = Calc()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
calc.setListener(mainListener)
}
作ったクラスを宣言してonCreateイベントで**calc.setListener(mainListener)**とメインのリスナーをセットします。
private val mainListener = object : CalcInterface{
override fun onSuccess(result: Int) {
super.onSuccess(result)
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText(result.toString())
}
override fun onDivZero() {
super.onDivZero()
val vtExe = findViewById<TextView>(R.id.tvExecute)
vtExe.setText("div zero error!")
}
}
MainActivityで受け取るためのリスナーを定義します。
上位のCalcInterfaceで定義したメソッドを継承することでイベントを受け取ることが出来ます。
fun onButtonClick(view: View){
val etV1 = findViewById<EditText>(R.id.etV1)
val etV2 = findViewById<EditText>(R.id.etV2)
calc.execute(etV1.text.toString().toInt(),etV2.text.toString().toInt())
}
ボタンクリックの時の処理を書いて終わりです。
最後に
Javaの方は独自のリスナー、独自イベントの説明があるのですがKotlinで説明しているサイトが見つかりませんでした。
今後独自のイベントを使うクラスを設計するときのメモとしてここに残しておきます。