ViewGroup のタッチイベントを管理する

でのタッチイベントの処理 ViewGroup が特別な対応をしています なぜなら、ViewGroup には、異なるプロジェクトに対してターゲットとなる子が ViewGroup 自体よりも多くのタッチイベントがあります。各ビューが正しく 新しいタッチイベントをオーバーライドして、 onInterceptTouchEvent() メソッドを呼び出します。

ViewGroup でタッチイベントをインターセプトする

onInterceptTouchEvent() メソッドは、タッチイベントが検出されると呼び出されます ViewGroup のサーフェス(子のサーフェスを含む)条件 onInterceptTouchEvent()true を返します。 MotionEvent インターセプトされます。つまり、子ではなく onTouchEvent() メソッドを呼び出します。

onInterceptTouchEvent() メソッドにより、親はタッチイベントを確認できるようになります。 制御できます。onInterceptTouchEvent() から true を返す場合、 それまでタッチイベントを処理していた子ビューが ACTION_CANCEL, それ以降のイベントは親の onTouchEvent() メソッドに送信されます 通常の処理を行いますonInterceptTouchEvent()false を返すこともできます。 イベントを監視し、ビュー階層を下って通常のターゲットまで移動します。 独自の onTouchEvent() を使用して作成できます。

次のスニペットでは、MyViewGroup クラスが ViewGroup を拡張しています。 MyViewGroup には複数の子ビューが含まれます。キッズビューで指をドラッグした場合 水平方向に移動すると、子ビューがタッチイベントを取得しなくなり、MyViewGroup がタップを処理します。 イベントを表示できます。ただし、子ビューのボタンをタップしたり、子をスクロールしたりすると 親がこれらのタッチイベントをインターセプトすることはありません。これは、子が意図したものであるためです。 あります。その場合、onInterceptTouchEvent()false を返し、 MyViewGroup クラスの onTouchEvent() は呼び出されません。

Kotlin

class MyViewGroup @JvmOverloads constructor(
        context: Context,
        private val mTouchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
) : ViewGroup(context) {
    ...
    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        // This method only determines whether you want to intercept the motion.
        // If this method returns true, onTouchEvent is called and you can do
        // the actual scrolling there.
        return when (ev.actionMasked) {
            // Always handle the case of the touch gesture being complete.
            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                // Release the scroll.
                mIsScrolling = false
                false // Don't intercept the touch event. Let the child handle it.
            }
            MotionEvent.ACTION_MOVE -> {
                if (mIsScrolling) {
                    // You're currently scrolling, so intercept the touch event.
                    true
                } else {

                    // If the user drags their finger horizontally more than the
                    // touch slop, start the scroll.

                    // Left as an exercise for the reader.
                    val xDiff: Int = calculateDistanceX(ev)

                    // Touch slop is calculated using ViewConfiguration constants.
                    if (xDiff > mTouchSlop) {
                        // Start scrolling!
                        mIsScrolling = true
                        true
                    } else {
                        false
                    }
                }
            }
            ...
            else -> {
                // In general, don't intercept touch events. The child view
                // handles them.
                false
            }
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        // Here, you actually handle the touch event. For example, if the action
        // is ACTION_MOVE, scroll this container. This method is only called if
        // the touch event is intercepted in onInterceptTouchEvent.
        ...
    }
}

Java

public class MyViewGroup extends ViewGroup {

    private int mTouchSlop;
    ...
    ViewConfiguration vc = ViewConfiguration.get(view.getContext());
    mTouchSlop = vc.getScaledTouchSlop();
    ...
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // This method only determines whether you want to intercept the motion.
        // If this method returns true, onTouchEvent is called and you can do
        // the actual scrolling there.

        final int action = MotionEventCompat.getActionMasked(ev);

        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mIsScrolling = false;
            return false; // Don't intercept touch event. Let the child handle it.
        }

        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                if (mIsScrolling) {
                    // You're currently scrolling, so intercept the touch event.
                    return true;
                }

                // If the user drags their finger horizontally more than the
                // touch slop, start the scroll.

                // Left as an exercise for the reader.
                final int xDiff = calculateDistanceX(ev);

                // Touch slop is calculated using ViewConfiguration constants.
                if (xDiff > mTouchSlop) {
                    // Start scrolling.
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
            ...
        }

        // In general, don't intercept touch events. The child view handles them.
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Here, you actually handle the touch event. For example, if the
        // action is ACTION_MOVE, scroll this container. This method is only
        // called if the touch event is intercepted in onInterceptTouchEvent.
        ...
    }
}

なお、ViewGroup には、 requestDisallowInterceptTouchEvent() メソッドを呼び出します。ViewGroup は、子が親とその親要素を必要としない場合に、このメソッドを呼び出します。 onInterceptTouchEvent() でタッチイベントをインターセプトする祖先を使用します。

ACTION_OUTSIDE イベントを処理する

ViewGroupMotionEventACTION_OUTSIDE, イベントはデフォルトでは子にディスパッチされません。次を使用して MotionEvent を処理する: ACTION_OUTSIDE、いずれかをオーバーライド dispatchTouchEvent(MotionEvent event) 適切な View にディスパッチするか、 関連する用語で Window.Callback - 例: Activity

ViewConfiguration 定数を使用する

上記のスニペットでは、現在の ViewConfiguration を使用して変数を初期化しています。 mTouchSlop と呼ばれます。ViewConfiguration クラスを使用すると、 Android システムで使用される一般的な距離、速度、時間。

「タッチスロップ」ユーザーのタップ操作が起きるまでに動き続けられる距離(ピクセル単位)です スクロールと解釈されますタッチスロップは通常、ユーザーが誤ってスクロールするのを防ぐために使用されます。 画面上の要素のタップなど、別のタップ操作を行っているとき。

よく使用される ViewConfiguration メソッドが他にも 2 つあります。 getScaledMinimumFlingVelocity() および getScaledMaximumFlingVelocity()。 これらのメソッドは、最小速度と最大速度をそれぞれ返し、測定されたフリングを開始します。 (ピクセル/秒)です例:

Kotlin

private val vc: ViewConfiguration = ViewConfiguration.get(context)
private val mSlop: Int = vc.scaledTouchSlop
private val mMinFlingVelocity: Int = vc.scaledMinimumFlingVelocity
private val mMaxFlingVelocity: Int = vc.scaledMaximumFlingVelocity
...
MotionEvent.ACTION_MOVE -> {
    ...
    val deltaX: Float = motionEvent.rawX - mDownX
    if (Math.abs(deltaX) > mSlop) {
        // A swipe occurs, do something.
    }
    return false
}
...
MotionEvent.ACTION_UP -> {
    ...
    if (velocityX in mMinFlingVelocity..mMaxFlingVelocity && velocityY < velocityX) {
        // The criteria are satisfied, do something.
    }
}

Java

ViewConfiguration vc = ViewConfiguration.get(view.getContext());
private int mSlop = vc.getScaledTouchSlop();
private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
...
case MotionEvent.ACTION_MOVE: {
    ...
    float deltaX = motionEvent.getRawX() - mDownX;
    if (Math.abs(deltaX) > mSlop) {
        // A swipe occurs, do something.
    }
...
case MotionEvent.ACTION_UP: {
    ...
    } if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
            && velocityY < velocityX) {
        // The criteria are satisfied, do something.
    }
}

子ビューのタップ可能エリアを拡張する

Android は、 TouchDelegate クラスで実現 親は、子ビューのタップ可能領域を子の境界を超えて拡張できます。この これは、子を小さくする必要があるものの、タップ領域を大きくする必要がある場合に便利です。これを使用して 子のタッチ領域を縮小することをおすすめします。

次の例では、 ImageButton は _delegate です。 ビュー(親がタッチ領域を拡張する子)を表します。レイアウト ファイルは次のとおりです。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/parent_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity" >

     <ImageButton android:id="@+id/button"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@null"
          android:src="@drawable/icon" />
</RelativeLayout>

次のスニペットは、これらのタスクを完了します。

  • 親ビューを取得し、Runnable を投稿します。 呼び出すことができますこれにより、親は、 getHitRect() メソッドを呼び出します。getHitRect() メソッドは、子のヒット長方形( タップ可能な領域)を親の座標に配置します。
  • ImageButton 子ビューを検索し、getHitRect() を呼び出して 子のタップ可能領域の境界を設定します
  • ImageButton 子ビューのヒット長方形の境界を拡張します。
  • TouchDelegate をインスタンス化し、拡大されたヒット長方形と パラメータとして ImageButton 子ビューを指定します。
  • タッチ デリゲート内でタップされるように、親ビューの TouchDelegate を設定します。 子にルーティングされます

親ビューは、ImageButton 子ビューのタップ デリゲートとして機能する すべてのタッチイベントを受け取ります。子のヒット長方形内でタッチイベントが発生すると、親は タッチイベントが子に渡され、処理されます。

Kotlin

public class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Post in the parent's message queue to make sure the parent lays out
        // its children before you call getHitRect().
        findViewById<View>(R.id.parent_layout).post {
            // The bounds for the delegate view, which is an ImageButton in this
            // example.
            val delegateArea = Rect()
            val myButton = findViewById<ImageButton>(R.id.button).apply {
                isEnabled = true
                setOnClickListener {
                    Toast.makeText(
                            this@MainActivity,
                            "Touch occurred within ImageButton touch region.",
                            Toast.LENGTH_SHORT
                    ).show()
                }

                // The hit rectangle for the ImageButton.
                getHitRect(delegateArea)
            }

            // Extend the touch area of the ImageButton beyond its bounds on the
            // right and bottom.
            delegateArea.right += 100
            delegateArea.bottom += 100

            // Set the TouchDelegate on the parent view so that touches within
            // the touch delegate bounds are routed to the child.
            (myButton.parent as? View)?.apply {
                // Instantiate a TouchDelegate. "delegateArea" is the bounds in
                // local coordinates of the containing view to be mapped to the
                // delegate view. "myButton" is the child view that receives
                // motion events.
                touchDelegate = TouchDelegate(delegateArea, myButton)
            }
        }
    }
}

Java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Get the parent view.
        View parentView = findViewById(R.id.parent_layout);

        parentView.post(new Runnable() {
            // Post in the parent's message queue to make sure the parent lays
            // out its children before you call getHitRect().
            @Override
            public void run() {
                // The bounds for the delegate view, which is an ImageButton in
                // this example.
                Rect delegateArea = new Rect();
                ImageButton myButton = (ImageButton) findViewById(R.id.button);
                myButton.setEnabled(true);
                myButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(MainActivity.this,
                                "Touch occurred within ImageButton touch region.",
                                Toast.LENGTH_SHORT).show();
                    }
                });

                // The hit rectangle for the ImageButton.
                myButton.getHitRect(delegateArea);

                // Extend the touch area of the ImageButton beyond its bounds on
                // the right and bottom.
                delegateArea.right += 100;
                delegateArea.bottom += 100;

                // Instantiate a TouchDelegate. "delegateArea" is the bounds in
                // local coordinates of the containing view to be mapped to the
                // delegate view. "myButton" is the child view that receives
                // motion events.
                TouchDelegate touchDelegate = new TouchDelegate(delegateArea,
                        myButton);

                // Set the TouchDelegate on the parent view so that touches
                // within the touch delegate bounds are routed to the child.
                if (View.class.isInstance(myButton.getParent())) {
                    ((View) myButton.getParent()).setTouchDelegate(touchDelegate);
                }
            }
        });
    }
}