web-dev-qa-db-ja.com

Android:onInterceptTouchEventとdispatchTouchEventの違いは?

AndroidのonInterceptTouchEventdispatchTouchEventの違いは何ですか?

Android開発者ガイドによると、両方の方法を使用してタッチイベント(MotionEvent)をインターセプトできますが、違いは何ですか?

onInterceptTouchEventdispatchTouchEvent、およびonTouchEventは、ビューの階層(ViewGroup)内でどのように相互作用しますか?

230
Anne Droid

これを分かりやすく説明するのに最適な場所は、ソースコードです。ドキュメントはこれを説明するのはひどく不十分です。

dispatchTouchEventは、実際にはActivity、View、ViewGroupで定義されています。 タッチイベントのルーティング方法を決定するコントローラーと考えてください。

たとえば、最も単純なケースはView.dispatchTouchEventの場合です。これは、タッチイベントを定義している場合はOnTouchListener.onTouchに、または拡張メソッドonTouchEvent

ViewGroup.dispatchTouchEventの場合、物事はずっと複雑です。 (child.dispatchTouchEventを呼び出すことにより)イベントを取得する子ビューの1つを把握する必要があります。これは基本的に、どの子ビューの境界矩形にタッチポイント座標が含まれているかを調べるヒットテストアルゴリズムです。

ただし、適切な子ビューにイベントをディスパッチする前に、親はイベントをまとめてスパイおよび/またはインターセプトできます。これがonInterceptTouchEventの目的です。したがって、ヒットテストを行う前にこのメソッドを最初に呼び出し、イベントがハイジャックされた場合(onInterceptTouchEventからtrueを返すことにより)、ACTION_CANCELを子ビューに送信して、タッチイベント処理を破棄できるようにします(前から)タッチイベント)そしてそれ以降、親レベルのすべてのタッチイベントはonTouchListener.onTouch(定義されている場合)またはonTouchEvent()にディスパッチされます。その場合も、onInterceptTouchEventが再び呼び出されることはありません。

[Activity | ViewGroup | View] .dispatchTouchEventをオーバーライドしますか?カスタムルーティングを行っているのでなければ、おそらくすべきではありません。

主な拡張メソッドは、親レベルでタッチイベントをスパイおよび/またはインターセプトする場合はViewGroup.onInterceptTouchEvent、メインイベント処理ではView.onTouchListener/View.onTouchEventです。

全体的に非常に複雑なデザインですが、Android AP​​Iは単純さよりも柔軟性に傾いています。

261
numan salati

これはGoogleでの最初の結果だからです。 Youtube:マスタリングAndroidタッチシステム とスライドが利用可能です こちら で、Dave Smithによる素晴らしいトークを共有したいと思います。 Android Touch Systemについて深く理解できました。

Activityがタッチを処理する方法:

  • Activity.dispatchTouchEvent()
    • 常に最初に呼び出される
    • Windowにアタッチされたルートビューにイベントを送信します
    • onTouchEvent()
      • ビューがイベントを消費しない場合に呼び出されます
      • 常に最後に呼び出される

Viewがタッチを処理する方法:

  • View.dispatchTouchEvent()
    • 存在する場合は、最初にイベントをリスナーに送信します
      • View.OnTouchListener.onTouch()
    • 消費されない場合、タッチ自体を処理します
      • View.onTouchEvent()

ViewGroupがタッチを処理する方法:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • 子供に取って代わるかどうかを確認する
      • ACTION_CANCELをアクティブな子に渡します
      • 一度trueを返し、後続のすべてのイベントを消費します
    • 子ビューごとに、逆の順序で追加されました
      • タッチが関連する場合(ビュー内)、child.dispatchTouchEvent()
      • 前で処理されなかった場合、次のビューにディスパッチします
    • 子がイベントを処理しない場合、リスナーはチャンスを取得します
      • OnTouchListener.onTouch()
    • リスナーがない場合、または処理されていない場合
      • onTouchEvent()
  • 傍受されたイベントは子ステップを飛び越える

また、カスタムタッチのサンプルコードを github.com/devunwired/ で提供しています。

Answer:基本的に、dispatchTouchEvent()がすべてのViewレイヤーで呼び出され、Viewが進行中のジェスチャーに興味があるかどうかを判断します。 ViewGroupでは、ViewGroupは、dispatchTouchEvent()- methodでタッチイベントをスチールする前に、子でdispatchTouchEvent()を呼び出す前に、 ViewGroupは、ViewGrouponInterceptTouchEvent()- methodがtrueを返す場合にのみディスパッチを停止します。 differenceは、dispatchTouchEvent()MotionEventsをディスパッチしていることと、onInterceptTouchEventがインターセプトする必要があるかどうかを指示することです(子供にMotionEventをディスパッチしない)またはない(子供への発送)

ViewGroupのコード これを多かれ少なかれ実行することを想像できます(非常に単純化されています):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
228
seb

補足回答

他の回答の視覚的な補足を以下に示します。私の完全な答えは こちら です。

enter image description here

enter image description here

ViewGroupdispatchTouchEvent()メソッドは、onInterceptTouchEvent()を使用して、(onTouchEvent()を使用して)タッチイベントを直ちに処理するか、その子のdispatchTouchEvent()メソッドへの通知を継続するかを選択します。

43
Suragch

これらのメソッドには多くの混乱がありますが、実際にはそれほど複雑ではありません。混乱のほとんどは、次の理由によるものです。

  1. View/ViewGroupまたはその子のいずれかがonTouchEventでtrueを返さない場合、MotionEvent.ACTION_DOWNに対してのみdispatchTouchEventおよびonInterceptTouchEventが呼び出されます。 onTouchEventのtrueがない場合、親ビューは、ビューがMotionEventsを必要としないと想定します。
  2. ViewGroupのどの子もonTouchEventでtrueを返さない場合、ViewGroupがonTouchEventでtrueを返しても、onInterceptTouchEventはMotionEvent.ACTION_DOWNに対してのみ呼び出されます。

処理順序は次のとおりです。

  1. dispatchTouchEventが呼び出されます。
  2. onInterceptTouchEventは、MotionEvent.ACTION_DOWNに対して、またはViewGroupのいずれかの子がonTouchEventでtrueを返したときに呼び出されます。
  3. onTouchEventは、ViewGroupの子で最初に呼び出され、どの子もtrueを返さない場合、View/ViewGroupで呼び出されます。

お子様のイベントを無効にせずにTouchEvents/MotionEventsをプレビューするには、次の2つのことを行う必要があります。

  1. dispatchTouchEventをオーバーライドしてイベントをプレビューし、super.dispatchTouchEvent(ev)を返します。
  2. onTouchEventをオーバーライドしてtrueを返します。そうしないと、MotionEvent.ACTION_DOWN以外のMotionEventを取得できません。

スワイプイベントのようなジェスチャを検出したい場合、ジェスチャを検出しなかった限り、子供の他のイベントを無効にせずに、次のようにすることができます。

  1. 上記のようにMotionEventsをプレビューし、ジェスチャーを検出したときにフラグを設定します。
  2. フラグが子供によるMotionEvent処理をキャンセルするように設定されている場合、onInterceptTouchEventにtrueを返します。また、次のMotionEvent.ACTION_DOWNまでonInterceptTouchEventが再度呼び出されないため、フラグをリセットするのにも便利な場所です。

FrameLayoutのオーバーライドの例(私の例は、Xamarin AndroidでプログラミングしているC#ですが、ロジックはJavaでも同じです):

public override bool DispatchTouchEvent(MotionEvent e)
{
    // Preview the touch event to detect a swipe:
    switch (e.ActionMasked)
    {
        case MotionEventActions.Down:
            _processingSwipe = false;
            _touchStartPosition = e.RawX;
            break;
        case MotionEventActions.Move:
            if (!_processingSwipe)
            {
                float move = e.RawX - _touchStartPosition;
                if (move >= _swipeSize)
                {
                    _processingSwipe = true;
                    _cancelChildren = true;
                    ProcessSwipe();
                }
            }
            break;
    }
    return base.DispatchTouchEvent(e);
}

public override bool OnTouchEvent(MotionEvent e)
{
    // To make sure to receive touch events, tell parent we are handling them:
    return true;
}

public override bool OnInterceptTouchEvent(MotionEvent e)
{
    // Cancel all children when processing a swipe:
    if (_cancelChildren)
    {
        // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down:
        _cancelChildren = false;
        return true;
    }
    return false;
}
17
Marcel W

私はこのウェブページで非常に直感的な説明に出会いました http://doandroids.com/blogs/tag/codeexample/ 。そこから撮影:

  • boolean onTouchEvent(MotionEvent ev)-このビューをターゲットとするタッチイベントが検出されるたびに呼び出されます
  • boolean onInterceptTouchEvent(MotionEvent ev)-このViewGroupまたはその子がターゲットとしてタッチイベントが検出されるたびに呼び出されます。この関数がtrueを返す場合、MotionEventはインターセプトされます。つまり、子に渡されるのではなく、このビューのonTouchEventに渡されます。
8
Krzysztow

dispatchTouchEventは、onInterceptTouchEventの前に処理します。

この簡単な例を使用して:

   main = new LinearLayout(this){
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            System.out.println("Event - onInterceptTouchEvent");
            return super.onInterceptTouchEvent(ev);
            //return false; //event get propagated
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            System.out.println("Event - dispatchTouchEvent");
            return super.dispatchTouchEvent(ev);
            //return false; //event DONT get propagated
        }
    };

    main.setBackgroundColor(Color.GRAY);
    main.setLayoutParams(new LinearLayout.LayoutParams(320,480));    


    viewA = new EditText(this);
    viewA.setBackgroundColor(Color.YELLOW);
    viewA.setTextColor(Color.BLACK);
    viewA.setTextSize(16);
    viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80));
    main.addView(viewA);

    setContentView(main);

ログは次のようになります。

I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent

したがって、これら2つのハンドラーで作業している場合は、dispatchTouchEventを使用して、最初のインスタンスでイベントを処理します。イベントはonInterceptTouchEventに進みます。

もう1つの違いは、dispatchTouchEventが「false」を返す場合、イベントは子(この場合はEditText)に伝播されないのに対し、onInterceptTouchEventでfalseを返す場合、イベントはEditTextにディスパッチされることです。

8
Dayerman

このビデオで答えを見つけることができます https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 および次の3つのビデオ。すべてのタッチイベントは非常によく説明されており、非常に明確で例が豊富です。

4
BlueMango

短い答え:dispatchTouchEvent()firstと呼ばれます。

短いアドバイス:は制御が難しいので、dispatchTouchEvent()をオーバーライドしないでください。パフォーマンスが低下する場合があります。私見、onInterceptTouchEvent()をオーバーライドすることをお勧めします。

ほとんどの回答では、アクティビティ/ビューグループ/ビューのフロータッチイベントについて明確に言及しているため、これらのメソッドのコードについては、ViewGroupに追加するだけです(dispatchTouchEvent()は無視します)。

onInterceptTouchEvent()が最初に呼び出され、ACTIONイベントがそれぞれ下に->移動->上に呼び出されます。 2つのケースがあります。

  1. return false 3つの場合(ACTION_DOWN、ACTION_MOVE、ACTION_UP)、親はこのタッチイベントを必要としないと見なすので、onTouch()の親は決して呼び出しません、しかしonTouch()は代わりに呼び出します;ただし、注意してください:

    • onInterceptTouchEvent()は、子がrequestDisallowInterceptTouchEvent(true)を呼び出さない限り、引き続きタッチイベントを受け取ります。
    • そのイベントを受け取る子供がいない場合(2つの場合に発生する可能性があります:ユーザーがタッチする位置に子供がいないか、子供がいるがACTION_DOWNでfalseを返します)、親はそのイベントを親のonTouch()に送り返します。
  2. 逆にtrueを返すの場合、親はこのタッチイベントを盗みますすぐに、onInterceptTouchEvent()はすぐに停止し、代わりにonTouch()が呼び出されます同様に、すべてのonTouch()が受け取る最後のアクションイベント-ACTION_CANCEL(したがって、親がタッチイベントを盗んだことを意味し、それ以降、子供はそれを処理できません)。 onInterceptTouchEvent()がfalseを返すフローは正常ですが、trueを返す場合と少し混乱しているため、ここにリストします。

    • ACTION_DOWNでtrueを返すと、親のonTouch()はACTION_DOWNを受け取ります再びおよび後続のアクション(ACTION_MOVE、ACTION_UP)。
    • ACTION_MOVEでtrueを返すと、親のonTouch()next ACTION_MOVE(onInterceptTouchEvent()のACTION_MOVEとは異なります)および後続のアクション(ACTION_MOVE、ACTION_UP)を受け取ります。
    • ACTION_UPでtrueを返します。親のonTouch()は、親がタッチイベントを盗むには遅すぎるため、まったく呼び出されませんではなくです。

もう1つ重要は、onTouch()のイベントのACTION_DOWNにより、ビューがそのイベントからさらにアクションを受け取りたいかどうかを決定します。ビューがonTouch()のACTION_DOWNでtrueを返す場合、それはビューがそのイベントからより多くのアクションを受け取る意思があることを意味します。そうでない場合、onTouch()のACTION_DOWNでfalseを返すと、ビューはそのイベントからそれ以上のアクションを受け取らないことを意味します。

3
Nguyen Tan Dat

ViewGroupサブクラス内の次のコードは、親コンテナーがタッチイベントを受信できないようにします。

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    // Normal event dispatch to this container's children, ignore the return value
    super.dispatchTouchEvent(ev);

    // Always consume the event so it is not dispatched further up the chain
    return true;
  }

これをカスタムオーバーレイで使用して、バックグラウンドビューがタッチイベントに応答しないようにしました。

3
James Wald

主な違い:

•Activity.dispatchTouchEvent(MotionEvent)-これにより、アクティビティはすべてのタッチイベントをウィンドウにディスパッチする前にインターセプトできます。
•ViewGroup.onInterceptTouchEvent(MotionEvent)-これにより、ViewGroupは、子ビューにディスパッチされるイベントを監視できます。

1
ChapMic

ViewGroupのonInterceptTouchEvent()は、常に最初に発生するイベントであるACTION_DOWNイベントのエントリポイントです。

ViewGroupでこのジェスチャを処理する場合は、onInterceptTouchEvent()からtrueを返します。 trueを返すと、ViewGroupのonTouchEvent()は、次のACTION_UPまたはACTION_CANCELまでのすべての後続イベントを受け取ります。ほとんどの場合、ACTION_DOWNACTION_UPまたはACTION_CANCELの間のタッチイベントはACTION_MOVEで、通常はスクロール/フリングジェスチャとして認識されます。

onInterceptTouchEvent()からfalseを返すと、ターゲットビューのonTouchEvent()が呼び出されます。 onInterceptTouchEvent()からtrueを返すまで、後続のメッセージに対して繰り返されます。

ソース: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

1
vipsy
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
0
Mc_fool_himself

私の意見では、dispatchTouchEvent()はデバイスのタッチを処理する上で非常に重要な部分です。 ActivityViewGroupViewには、このメソッドが実装されています。 ViewGroupのメソッドを考慮すると、dispatchTouchEvent()onInterceptTouchEvent()を呼び出すことがわかります

enter image description here

完全なSO答えは ここ です

0
yoAlex5

ActivityとViewの両方にdispatchTouchEvent()メソッドとonTouchEventメソッドがあります。ViewGroupにもこのメソッドがありますが、onInterceptTouchEventという別のメソッドがあります。これらのメソッドの戻り値の型はブール値であり、戻り値を介してディスパッチルートを制御できます。

Androidのイベントディスパッチは、Activity-> ViewGroup-> Viewから始まります。

0
Ryan