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


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

Я вставлю комментарий сюда:

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

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

2 15

2 ответа:

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

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

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

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

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

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

Public void requestDisallowInterceptTouchEvent (boolean disallowIntercept)

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

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

Этот ответ:
https://stackoverflow.com/a/13540006/3956566 дает хорошую визуализацию того, как проходит распространение сенсорных событий через:
parent -> child|parent -> child|parent -> child views.

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

Этот пример взят из управление сенсорными событиями в группе просмотра и демонстрирует, как перехватить 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;
}

Edit: чтобы ответить на комментарии.
Это некоторый код из той же ссылки, показывающий, как создать параметры прямоугольника вокруг вашего элемента:
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);
}

Давайте исправим проблему.

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

Я уже давно ищу ответ на один и тот же вопрос. Не сумел найти ничего разумного и поэтому самостоятельно придумал следующее решение.

Следующий фрагмент кода предоставляет обзор соответствующего кода 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;
}