Androidでのイベント配信メカニズム(下)——Viewのイベント処理


総括する
前編のAndroidにおけるイベント配信メカニズム(上)−ViewGroupのイベント配信では,ViewGroupのイベント配信について詳細に分析した.記事の最後に、ViewGroupのdispatchTouchEventメソッドは、dispatchTransformedTouchEventメソッドを呼び出し、イベントをViewGroupのサブViewに渡すことに成功した.サブViewに処理を依頼します.では、イベントを一度にビューが受信した後、どのように処理されたかを分析します.
Viewのイベント処理
ここで説明するビューについては、ビューグループの親であり、子要素は含まれていません.これは、Viewが再びイベントを下に配布することができないことを意味します.そのため、ViewにはonInterceptTouchEventメソッドは存在せず、イベントをブロックすることもありません.受信したイベントを処理することです.次に、ビューがイベントをどのように処理しているかを見てみましょう.ViewGroupがイベントをViewdispatchTouchEvent側に渡す以上.ではまずここでdispatchTouchEventで何をしたかを見てみましょう.
public boolean dispatchTouchEvent(MotionEvent event) {

    ......

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    ......

    return result;
}

ViewのdispatchTouchEventメソッドでは、イベント処理のコア部分が上記のコードに反映される.onFilterTouchEventForSecurityメソッドは、現在受信されているイベントのviewが上書きされているかどうかを示し、Viewが上書きされている状態は、現在のViewが最上位に位置していないことを示し、そのviewが他のViewに上書きされている.現在のViewが隠されている場合、そのViewはイベントを処理しません.
public interface OnTouchListener {
    boolean onTouch(View v, MotionEvent event);
}

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

上記のコードを組み合わせると、setOnTouchListenerメソッドでOnTouchListenerを設定した後、現在のViewが使用可能な状態であれば、条件li!=null && li.mOnTouchListener !=null&&(mViewFlags&ENABLED_MASK)==ENABLEDは必ずtrueです.このときプログラムはOnTouchListenerのonTouchメソッドをコールバックし,onTouchメソッドでtrueを返すと,ViewのonTouchEventメソッドは実行されない.ここから、OnTouchListenerが設定されると、OnTouchListenerの優先度はonTouchEventよりも高くなることがわかります.プログラムにOnTouchListenerが設定されている場合、OnTouchListenerのonTouchに対する戻り値は、ViewのdispatchTouchEventメソッドが返す値を表していないことに注意してください.onTouchメソッドがtrueを返すと、イベントが現在のViewによって消費されることに成功し、resultがtrueに設定され、onTouchEventが実行されなくなるため、dispatchTouchEventもtrueを返す.しかし、いったんonTouchメソッドでfalseを返すと.このときonTouchEventメソッドが呼び出され、イベントがonTouchEventによって正常に処理され、trueが返されるとresultはtrueに設定され、dispatchTouchEventは自然にtrueに戻ります.次に、Viewに入るonTouchEventメソッドを探ってみましょう.onTouchEventメソッドの内容が多いので、ここでセグメント化して見ます.
if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
    // A disabled view that is clickable still consumes the touch
    // events, it just doesn't respond to them.
    return (((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}

ここでは、利用できないViewに対して、彼らのいくつかのクリックイベントが利用可能であれば、依然として消費イベントに成功することができますが、イベントに応答しません.ビューのこれらのクリック間はデフォルトでは使用できませんが、異なるビューではデフォルト値が異なります.たとえば、ImageViewではクリックイベントは使用できませんが、Buttonではクリックイベントが使用可能です.もちろん、手動でリスニング・イベントを設定すると、これらのリスニング・イベントは自動的に使用可能な状態に設定されます.以下のソースコードからわかります.
public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
    if (!isLongClickable()) {
        setLongClickable(true);
    }
    getListenerInfo().mOnLongClickListener = l;
}

public void setOnContextClickListener(@Nullable OnContextClickListener l) {
    if (!isContextClickable()) {
        setContextClickable(true);
    }
    getListenerInfo().mOnContextClickListener = l;
}

次にOnTouchEventのコードを見てみましょう.
if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
        return true;
    }
}

ここではまず,イベントにエージェントが設定されているかどうかを判断し,イベントにエージェントが設定されている場合にはTouchDelegateのonTouchEventメソッドを実行する.mTouchDelegateのデフォルト値はnullで、ViewのsetTouchDelegateメソッドでエージェントを設定できます.TouchDelegateについては後述するが,ここではあまり説明しない.最後にViewがどのようにイベントを処理しているかを見てみましょう.受信したイベント全体の処理過程は複雑で、ここではマクロ的に全体的にその処理メカニズムを見てみましょう.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
        (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    switch (action) {
        case MotionEvent.ACTION_UP:

            ......

                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    // This is a tap, so remove the longpress check
                    removeLongPressCallback();

                    // Only perform take click actions if we were in the pressed state
                    if (!focusTaken) {
                        // Use a Runnable and post this rather than calling
                        // performClick directly. This lets other visual state
                        // of the view update before click actions start.
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }

            ......

            break;

        ......

    }

    return true;
}

ビューのクリックイベントが利用可能な状態であれば、これらのイベントは処理され、trueに戻ります.イベントシーケンスが完了するとperformClickメソッドが呼び出されます.次に、このperformClickメソッドを参照してください.
public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

上記のコードから、OnClickListenerを設定すると、onClickメソッドが呼び出されます.このようにして、ViewのonClickイベントについては、最後に呼び出され、onClickの優先度が最も低いことがわかります.
まとめ
ここではViewのイベント処理についてまとめます.ViewGroupがイベントをViewに配布した後.イベントは、View内でOnTouchListenerのonTouchとView内のonTouchEventの2つの方法で処理されます.onTouchメソッドはViewがユーザに提供するものであり,ユーザがタッチイベントを自分で処理するのに便利であるが,onTouchEventはAndroidシステムが自分で実現するインタフェースである.ユーザーがOnTouchListenerを設定すると、AndroidシステムはまずOnTouchListenerのonTouchメソッドを呼び出します.onTouchメソッドでtrueを返すと、ViewのonTouchEventメソッドは実行されません.onTouchでfalseが返された場合にのみonTouchEventが実行されます.