banner
jzman

jzman

Coding、思考、自觉。
github

Androidイベントディスパッチソースコード分析

PS:もしあなたが努力していると思うなら、あなたの努力の結果は何ですか?

前の記事では、Android のイベント配信の大まかな流れについて説明しました。以下では、Activity、ViewGroup、View の 3 つの観点からイベントに関連するメソッドを紹介します。小節は以下の通りです:

  1. Activity
  2. ViewGroup
  3. View

Activity#

Activity には、イベント伝達に関連する主な 2 つのメソッド、dispatchTouchEvent () と onTouchEvent () があります。イベントの伝達は、Activity の dispatchTouchEvent () メソッドから始まります。

イベント配信#

Activity のイベント配信メソッド:dispatchTouchEvent ()、そのソースコードは以下の通りです:

 
//イベント配信
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //空のメソッド
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //onTouchEvent()メソッドはデフォルトでfalseを返す
    return onTouchEvent(ev);
}

上記のコードでは明らかに ACTION_DOWN イベントのみが処理されており、ACTION_DOWN イベントがイベントの配信をトリガーすることを示しています。次に、Window クラスの superDispatchTouchEvent (ev) メソッドが呼び出されます。これは抽象メソッドであり、このメソッドが呼び出されると、具体的なサブクラスのメソッドが呼び出されます。Window クラスの具体的なサブクラスは PhoneWindow クラスであり、その具体的な実装の superDispatchTouchEvent (ev) メソッドは以下の通りです:

//Windowクラス内の抽象メソッド
public abstract boolean superDispatchTouchEvent(MotionEvent event);
//WindowサブクラスPhoneWindowクラス内のsuperDispatchTouchEvent()メソッドの具体的な実装
@Override  
public boolean superDispatchTouchEvent(MotionEvent event) {  
    return mDecor.superDispatchTouchEvent(event);  
}

明らかに、ここでは mDecor.superDispatchTouchEvent (event) が呼び出されており、mDecor は PhoneWindow.DecorView オブジェクトであり、これがウィンドウの最上位ビューです。これは本当の Activity のルートビューであり、FrameLayout を継承しています。super.dispatchTouchEvent を通じて、タッチイベントは各 Activity の子ビューに配信されます。これは、Activity.onCreate メソッドで setContentView 時に設定したビューです。コードの参考は以下の通りです:

//DecorViewクラスの宣言
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ...
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //ここでFrameLayout内のdispatchTouchEventメソッドが呼び出されます
        return super.dispatchTouchEvent(event);
    } 
    ...
}

FrameLayout では dispatchTouchEvent (event) メソッドがオーバーライドされていないため、FrameLayout の親クラスである ViewGroup 内のそのメソッドの実装が必要です。このメソッドがイベントの具体的な配信を行います。ここでの具体的なイベント配信プロセスは研究の余地があります。

イベント処理#

Activity 内のイベント処理メソッド:onTouchEvent ()、そのソースコードは以下の通りです:

//イベント処理、デフォルトでfalseを返す
public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        //デフォルトでfalseを返す
        return false;
    }

onTouchEvent () メソッドにとって、イベント伝達は親コントロールに伝達されます。たとえ false を返しても、イベントは消費されたことになります。

注意:Activity がイベントを配信する際、return super.dispatchTouchEvent (ev) のときのみ、イベントは下に伝達され続けます。true または false を返すと、イベントは消費され、つまりイベントの伝播が終了します。

ViewGroup#

ViewGroup には、イベント伝達に関連する主な 3 つのメソッド:dispatchTouchEvent ()、onInterceptTouchEvent ()、および onTouchEvent () があります。具体的には以下の通りです:

イベント配信#

ViewGroup のイベント配信メソッド:dispatchTouchEvent ()、そのソースコードは以下の通りです:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    //dispatchTouchEvent()メソッドはデフォルトでfalseを返す
    boolean handled = false;
    
    //メソッドがイベントの配信を中断するかどうかを決定します
    onInterceptTouchEvent(ev);
    ...
    //デフォルトではcanceledとinterceptedはfalse
    if (!canceled && !intercepted) {
        ...
        //このメソッドはイベントを子Viewに渡します
        dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        ...        
    }
            
    return handled;
}

このメソッドの役割は、ViewGroup 内の子 View を遍歴し、イベント (ACTION_DOWN) を子 View に処理させることです。主に onInterceptTouchEvent () と dispatchTransformedTouchEvent () メソッドが呼び出されます。onInterceptTouchEvent () はデフォルトで false を返します。以下は dispatchTransformedTouchEvent () メソッドの主要なソースコードです:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits){
    ...
    
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        //子Viewのイベント配信を行います
        handled = child.dispatchTouchEvent(event);
    }    
    ...
    return handled;
}

明らかに、dispatchTransformedTouchEvent () メソッドは主に子 View のイベント配信を行います。子 View がない場合は、親 View の dispatchTouchEvent (event) メソッドを呼び出します。

イベントの中断#

ViewGroup 内のイベント配信メソッド onInterceptTouchEvent ()、そのソースコードは以下の通りです:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    ...
    //デフォルトでfalseを返す
    return false;
}

このメソッドはデフォルトで false を返し、子 View へのイベント配信を中断しないことを示します。このメソッドは ViewGroup の dispatchTouchEvent () メソッド内で呼び出されます。

イベント処理#

ViewGroup には独自の onTouchEvent () イベント処理メソッドはなく、View を継承しており、そのイベント処理メソッドは View のイベント処理メソッドです。そのメソッドは以下の通りです:

public boolean onTouchEvent(MotionEvent event) {
    ...
    //デフォルトでfalseを返す
    return false;
}

このメソッドは関連するイベントを処理します。true を返す場合、イベントが処理されたことを示し、具体的な使用状況は後の文で記録されます。

View#

View には、イベント配信に関連する主な 2 つのメソッド:dispatchTouchEvent () と onTouchEvent () メソッドがあります。具体的には以下の通りです:

イベント配信#

View のイベント配信メソッドは dispatchTouchEvent () であり、主なコードは以下の通りです:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    //デフォルトの返り値はfalse
    boolean result = false;
    ...
    if (onFilterTouchEventForSecurity(event)) {
        ...
        //onTouchEventメソッドが呼び出されます
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
    return result;   
}

上記のコードでは、dispatchTouchEvent () メソッドのデフォルトの返り値は false であり、イベントは引き続き配信されます。実際には dispatchTouchEvent メソッドの返り値は onTouchEvent メソッドの返り値に依存します。onTouchEvent が true を返すと、dispatchTouchEvent の返り値 result は true となり、この時点でイベントは消費されたことを示します。もちろん、onTouchEvent の値が true であること自体がイベントが消費されたことを示します。以下は onTouchEvent メソッドが実行される条件です:

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //noinspection RedundantIfStatement
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        //ウィンドウが遮られているため、タッチイベントを破棄します(ほとんど実行されません)
        return false;
    }
    return true;
}

明らかに、上記のメソッドは一般的な状況では true を返すため、onTouchEvent メソッドが実行されることが確実です。View の dispatchTouchEvent メソッドは実際には以下のように簡略化できます:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    //デフォルトの返り値はfalse
    boolean result = false;
    ...
    return onTouchEvent(event)
}

イベント処理#

View のイベント処理メソッドは onTouchEvent () であり、主なコードは以下の通りです:

public boolean onTouchEvent(MotionEvent event) {
    ...
    
    //ここでtrueを返す条件はTouchDelegateがデフォルトで空であることです。この値はViewのタッチ領域に関するものです
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    //クリックイベントが設定されている場合、この条件はtrueを返します。つまり、イベントが消費されます
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
           //各イベントの処理
           ...
        }

        return true;
    }
    //デフォルトでfalseを返し、イベントを消費しないことを示します
    return false;
}

イベントが子 View に渡されると、2 つの結果があります。現在の View がそのイベントを消費するか、消費せずに上に戻すかです。中断せずに Activity まで到達し、何の処理も行わない場合、最終的にそのイベントは破棄されます。

この記事は Android のイベント配信メカニズムの第 2 篇であり、主に Activity、ViewGroup、View 内のイベントに関連するメソッド、つまり dispatchTouchEvent ()、onTouchEvent ()、および onInterceptTouchEvent () メソッドの Activity、ViewGroup、View 内での異なる表現を記録しました。コードの観点から Android のイベント関係を理解し、イベントの配信プロセスについては次の記事で具体的に説明します。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。