Android Studio Listenerの仕組みを作る メモ
coskx 20170703

androoid studio で,あるイベントが生じたことを知らせる機構(listener)を自分で作成するためのメモ

【1】作業の目的
あるイベントが生じたことを知らせる機構(listener)を自分で作成する。
listenerはボタンがクリックされたときに,MainActivityにそのことを知らせて,処理をさせるような場合に使われている。
これと同じような仕組みを自分で作る場合のエッセンスを抽出する。

【2】作業の大枠
あるクラスのインスタンス中であるイベントが発生したとき,そのクラスを生成したクラスのインスタンスにイベントが発生したことを伝える仕組みを作る。
【3】作業の詳細
MainActivityによって生成されたクラスEventdetectorがあり,このクラスのインスタンス中でイベントが発生したときに,MainActivity(のインスタンス)に知らせるものとする。
イベント発生を受け取るのはMainActivity(のインスタンス)とは限らないが,ここではMainActivity(のインスタンス)ということにしておく。

MainActivity(のインスタンス)
↑イベントの発生
EventDetectorのインスタンスmEventDetector

上記の部分だけ作ればよいのだが,例として簡単な疑似イベントが必要なため,次のような疑似イベントを使う。
EventDetectorが管理しているボタンがクリックされると,mEventDetectorに通知される。
この通知の回数(=ボタンの押された回数)を数え,回数が5の倍数になったら,疑似イベントとみなしMainActivityに知らせる。
着目するのは下図でオレンジ色の「mEventDetectorのイベント発生を,MainActivityに知らせる」ところである。

MainActivity(のインスタンス)
↑疑似イベントの発生
EventDetectorのインスタンスmEventDetector
(ボタンイベントが5回起こるごとに1回の疑似イベントを発生)
↑ボタンイベントの発生
ボタン


この仕組みは,「EventDetectorのインスタンスmEventDetector」から,「MainActivity」中のメソッドを呼び出すことができれば実現できる。
そのメソッドは「MainActivity」中のメソッドであるから,「MainActivity」中の変数やインスタンスにアクセス可能なメソッドになる。

Listenerというのは,上記の要求をスマートに記述する方法である。
「リスナinterface」(メソッドの名前だけを定義)を「EventDetector」から見えるところに定義し,「リスナinterface」を 「MainActivity」中で実体化し,メソッド本体もoverrideを使って「MainActivity」中で定義する。
「MainActivity」中で「EventDetector」のインスタンスを作り,このインスタンスに「リスナ interface」のインスタンスを登録する。
「EventDetector」のインスタンス「mEventDetector」は教えてもらった「リスナinterface」中のメソッドを呼び出すことができるようになっている。
「リスナinterface」と,イベント発生を感知しMainActivityに知らせる「イベント感知クラスEventDetector」を別ファイルとして用意する。

(1)リスナ interface: 
interface TestListener (TestListener.java)
(2)イベント感知クラス: class EventDetector (EventDetector.java)

コーディングはいろいろあるが,リスナ interfaceを使用する方法と,リスナ interfaceを省略する方法を示す。
また,複数のイベントを扱う例も示す。

サンプルファイル

【4】コーディング1 (リスナ interfaceを使用する)
1.layout を表すxml中にボタンを1つ作成し,そのボタンのIDを'button0'とする。

android:id="@+id/button0"

2.MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

//MainActivityはボタンがクリックされたことはわからないが,ボタンが5の倍数回押されたら,
//疑似イベントとしてmEventDetectorから報告を受ける。
public class MainActivity extends AppCompatActivity {

    private int eventcount=0;
    private EventDetector mEventDetector= new EventDetector(); //イベントが発生する場所
    private TestListener mTestListener
            = new TestListener() {
        @Override
        public void testtestSC() {     //イベントお知らせメソッド
            eventcount++;
            Toast.makeText(MainActivity.this, "LISTENER: event detected ("
                    + eventcount + ")", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEventDetector.initEventDetector(this);       //mEventDetectorを初期化
        mEventDetector.setListener(mTestListener);    //mEventDetectorにmTestListenerを登録
    }
}

3.TestListener.java

//リスナーのひな型
public interface TestListener {
    public void testtestSC();
}


4.EventDetector.java

import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.Button;

// 疑似イベント発生クラス
// ボタンが5の倍数回押されたときに疑似イベントが発生する。
public class EventDetector {
    private TestListener mTestListenerED;
    private int mCount=0;
    private Button mButton;

    public void setListener(TestListener tl) {
        mTestListenerED=tl;
    }

    public void initEventDetector(MainActivity mMainActivity) {
        Button mButton=(Button) mMainActivity.findViewById(R.id.button0);

        //ボタンイベントを取得し5n回目だったら疑似イベントを発生。
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "BUTTON: clicked", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
                if (++mCount%5==0) {
                    mTestListenerED.testtestSC();         //MainActivityにイベント発生を通知
                }
            }
        });
    }
}


【5】補足説明
(1)MainActivity.java  (class MainActivity)

private TestListener mTestListener
リスナー(通知の受け手)のインスタンスを定義し,通知してくるメソッドをoverrideで定義しなおしておく。
メソッドtesttestSC()は通知してくるメソッドになる。
このクラスTestListenerはクラスMainActivity内に定義されているため,メソッドtesttestSC()はクラスMainActivity中のeventcountのような変数やインスタンスにアクセスできる。

private EventDetector mEventDetector= new EventDetector();
疑似イベント発生源のインスタンスを生成する。
mEventDetector.setListener(mTestListener);
リスナーのインスタンス(mTestListener)を教えておく。


(2)TestListener.java  (interface TestListener)

「interface」はclassの特別の定義で,メソッドの名前や引数のみ定義している。
そのメソッドの実装は使われる場所(ここではMainActivity中)でoverrideで定義される。
しかし,メソッドの名前や引数はこのinterfaceの定義により公になっているので,EventDetector内で使うことが出来る。

(3)EventDetector.java  (class EventDetector)

public void setListener(TestListener tl)
MainActivityのmTestListenerを受け取って,それをmTestListenerEDとして保存しておく。
イベント発生時にはmTestListenerEDのメソッド,例えばtesttetssc(),を呼び出して,イベントをMainActivityのmTestListenerに
通知する。


【6】コーディング2 (リスナ interfaceを使用しない)

コーディング1をよく見ると,リスナinterfaceを省略することができることに気づく。
EventDetector.javaだけで,リスナのからくりを作ってみる。
layoutへのボタンの定義はコーディング1のときと同じようになっているとする。

1.MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

//MainActivityはボタンがクリックされたことはわからないが,ボタンが5の倍数回押されたら,
//疑似イベントとしてmEventDetectorから報告を受ける。
public class MainActivity extends AppCompatActivity {

    private int eventcount=0;
    private EventDetector mEventDetector
            = new EventDetector()
{
        @Override
        public void testtestSC() {     //イベントお知らせメソッド
            eventcount++;
            Toast.makeText(MainActivity.this, "LISTENER: event detected ("
                    + eventcount + ")", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEventDetector.initEventDetector(this);       //mEventDetectorを初期化
        mEventDetector.setListener(mEventDetector);     //mEventDetectorにリスナとしてmEventDetectorを登録
    }
}


2.EventDetector.java

import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.Button;

// 疑似イベント発生クラス
// ボタンが5の倍数回押されたときに疑似イベントが発生する。
public class EventDetector {
    private EventDetector mListenerED;
    private int mCount=0;

    public void setListener(EventDetector ls) {
        mListenerED=ls;
    }

    public void initEventDetector(MainActivity mMainActivity) {
        Button mButton=(Button) mMainActivity.findViewById(R.id.button0);

        //ボタンイベントを取得し5n回目だったら疑似イベントを発生。
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "BUTTON: clicked", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
                if (++mCount%5==0) {
                    mListenerED.testtestSC();         //MainActivityにイベント発生を通知
                }
            }
        });
    }

    public void testtestSC() {} //これはオーバーライド用
}