Как варьироваться между дочерними и родительскими группами событий касания

Я решил опубликовать этот вопрос и ответить в ответ на этот комментарий на этот вопрос:
 Как обрабатывать щелчок в дочерних представлениях и коснуться в родительских группах представлений?

Вставьте комментарий здесь:

Предположим, что я хочу переопределить события касания только для обработки некоторых дети, что я могу сделать внутри этой функции, чтобы она работала? Я имею в виду, для некоторых детей это будет работать как обычно, а для некоторых parent-view решит, будут ли они получать события касания или нет.

Итак, вопрос заключается в следующем: как предотвратить родительский onTouchEvent() от переопределения некоторых дочерних элементов onTouchEvent(), если он переопределяет функции других дочерних элементов?

Ответ 1

  • onTouchEvents() для групп вложенных представлений можно управлять с помощью boolean onInterceptTouchEvent.

Значение по умолчанию для OnInterceptTouchEvent равно false.

Родительский onTouchEvent принимается перед дочерним. Если OnInterceptTouchEvent возвращает false, он отправляет событие движения по цепочке дочернему обработчику onTouchEvent. Если он возвращает true, родитель будет обрабатывать событие касания.

Однако могут быть случаи, когда мы хотим, чтобы некоторые дочерние элементы управляли onTouchEvent, а некоторые из них управлялись родительским представлением (или, возможно, родителем родителя).

Это может управляться более чем одним способом.

  1. Один из способов защиты дочернего элемента от родительского OnInterceptTouchEvent заключается в реализации requestDisallowInterceptTouchEvent.

public void requestDisallowInterceptTouchEvent (boolean disallowIntercept)

Это предотвращает управление одним из родительских представлений onTouchEvent для этого элемента, если элемент имеет обработчики событий.

  1. Если значение OnInterceptTouchEvent равно false, будет вычисляться дочерний элемент onTouchEvent. Если у вас есть методы в дочерних элементах, обрабатывающих различные события касания, любые связанные обработчики событий, которые отключены, возвращают OnTouchEvent родительскому элементу.

Этот ответ:
fooobar.com/questions/22861/... дает хорошую визуализацию того, как проходит распространение сенсорных событий:
 parent -> child|parent -> child|parent -> child views.

  1. Другой способ возвращает переменные значения из OnInterceptTouchEvent для родителя.

В этом примере взята из Управление событиями Touch Touch в группе ViewGroup и демонстрирует, как перехватить дочерний элемент onTouchEvent при прокрутке пользователя.

4а.

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    /*
     * This method JUST determines whether we want to intercept the motion.
     * If we return true, onTouchEvent will be called and we 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; // Do not intercept touch event, let the child handle it
    }

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

            // If the user has dragged her finger horizontally more than 
            // the touch slop, start the scroll

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

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

    // In general, we don't want to intercept touch events. They should be 
    // handled by the child view.
    return false;
}

Изменить: ответить на комментарии.
Это код из той же ссылки, показывающий, как создать параметры прямоугольника вокруг вашего элемента:
4b.

// 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 should receive motion
// events.
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton);

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

Ответ 2

Позволяет изменить проблему.

У вас, похоже, есть группа ViewGroup с кучей детей. Вы хотите перехватить событие touch для всего, что связано с этой ViewGroup, за исключением некоторых детей.

Я давно искал ответ на тот же вопрос. Не удалось найти что-либо разумное и, таким образом, придумал свое решение со следующим решением.

В следующем фрагменте кода представлен обзор соответствующего кода ViewGroup, который перехватывает все касания, за исключением тех, которые исходят из представлений, которые имеют специальный набор тегов (вы должны установить его в другом месте вашего кода).

private static int NO_INTERCEPTION;

private boolean isWithinBounds(View view, MotionEvent ev) {
    int xPoint = Math.round(ev.getRawX());
    int yPoint = Math.round(ev.getRawY());
    int[] l = new int[2];
    view.getLocationOnScreen(l);
    int x = l[0];
    int y = l[1];
    int w = view.getWidth();
    int h = view.getHeight();
    return !(xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    for (int i=0; i<floatingMenuItems.getChildCount(); i++){
        View child = floatingMenuItems.getChildAt(i);
        if (child == null || child.getTag(NO_INTERCEPTION) == null) {
            continue;
        }
        if(isWithinBounds(child, ev)){
            return false;
        }
    }
    return true;
}