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 のイベント関係を理解し、イベントの分配プロセスについては次の記事で詳しく説明します。

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