Подтвердить что ты не робот

Android - перехват и передача всех событий касания

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

Меня интересуют все MotionEvents (не только DOWN), поэтому onInterceptTouchEvent() здесь не применяется, как если бы я возвращал true, мой оверлей будет потреблять все события, а если false будет принимать только события DOWN (то же самое относится к onTouch).

Я думал, что могу переопределить Activitys dispatchTouchEvent (MotionEvent ev) и вызвать пользовательское событие касания в моем наложении, но это приводит к тому, что не переводятся входные координаты в зависимости от положения моего представления (например, все события пройдут как представляется, происходит примерно на 20 пикселей ниже фактического касания, поскольку системная панель не учитывается).

4b9b3361

Ответ 1

Я решил это с не идеальным решением, но работает в этой ситуации.

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

Легко!

Ответ 2

Единственный способ создания такого перехватчика - использовать настраиваемый макет ViewGroup.

Но реализация ViewGroup.onInterceptTouchEvent() просто недостаточна в любом случае, потому что дочерние представления могут вызывать ViewParent.requestDisallowInterceptTouchEvent(), и если ребенок делает это, ваше представление прекратит принимать вызовы на interceptTouchEvent (см. здесь для примера, когда я ребенок, вы можете это сделать).

Здесь класс, который я написал и использовал, когда мне нужно что-то вроде этого, он настраивает FrameLayout, который дает полный контроль над событиями касания на любом делекторе

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;

/**
 * A FrameLayout that allow setting a delegate for intercept touch event
 */
public class InterceptTouchFrameLayout extends FrameLayout {
    private boolean mDisallowIntercept;

    public interface OnInterceptTouchEventListener {
        /**
         * If disallowIntercept is true the touch event can't be stealed and the return value is ignored.
         * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
         */
        boolean onInterceptTouchEvent(InterceptTouchFrameLayout view, MotionEvent ev, boolean disallowIntercept);

        /**
         * @see android.view.View#onTouchEvent(android.view.MotionEvent)
         */
        boolean onTouchEvent(InterceptTouchFrameLayout view, MotionEvent event);
    }

    private static final class DummyInterceptTouchEventListener implements OnInterceptTouchEventListener {
        @Override
        public boolean onInterceptTouchEvent(InterceptTouchFrameLayout view, MotionEvent ev, boolean disallowIntercept) {
            return false;
        }
        @Override
        public boolean onTouchEvent(InterceptTouchFrameLayout view, MotionEvent event) {
            return false;
        }
    }

    private static final OnInterceptTouchEventListener DUMMY_LISTENER = new DummyInterceptTouchEventListener();

    private OnInterceptTouchEventListener mInterceptTouchEventListener = DUMMY_LISTENER;

    public InterceptTouchFrameLayout(Context context) {
        super(context);
    }

    public InterceptTouchFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public InterceptTouchFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public InterceptTouchFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyle) {
        super(context, attrs, defStyleAttr, defStyle);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
        mDisallowIntercept = disallowIntercept;
    }

    public void setOnInterceptTouchEventListener(OnInterceptTouchEventListener interceptTouchEventListener) {
        mInterceptTouchEventListener = interceptTouchEventListener != null ? interceptTouchEventListener : DUMMY_LISTENER;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean stealTouchEvent = mInterceptTouchEventListener.onInterceptTouchEvent(this, ev, mDisallowIntercept);
        return stealTouchEvent && !mDisallowIntercept || super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = mInterceptTouchEventListener.onTouchEvent(this, event);
        return handled || super.onTouchEvent(event);
    }
}

Этот класс предоставляет setInterceptTouchEventListener() для установки вашего настраиваемого перехватчика.

Когда требуется запретить перехватывание события касания, он обманывает, он передает это родительскому виду, но продолжает перехватывать их. Тем не менее, это не позволяет событиям перехвата прослушивателя больше, поэтому, если слушатель возвращает true при перехвате события касания, которое будет проигнорировано.

Таким образом, вы можете прозрачно получать каждое событие касания, которое проходит через вашу ViewGroup, не нарушая поведения просмотров детей.

Ответ 3

Вот что я сделал, опираясь на ответ Дори, я использовал onInterceptTouch.,

float xPre = 0;

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if(Math.abs(xPre - event.getX()) < 40){
        return false;
    }
    if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP  || event.getAction() == MotionEvent.ACTION_MOVE){
        xPre = event.getX();
    }
    return true;
}

В принципе, когда происходит какое-либо важное движение, помните, где это произошло, через xPre; то если эта точка достаточно близка к следующей точке, чтобы сказать, onClick/onTouch дочернего представления возвращает false и позволяет onTouch делать это по умолчанию.

Ответ 4

По мере того, как потоки касаний, начиная с корневого представления и заканчивая дочерними представлениями, и после достижения нижней части его дочернего элемента (касания) распространяются обратно в корневой вид, просматривая все OnTouchListener, если действия, потребляемые дочерними представлениями, возвращают true и false для отдыха, тогда для действий, которые вы хотите перехватить, может быть написано в OnTouchListner этих родителей.

Как и в случае, если в корневом представлении A есть два дочерних X и Y, и вы хотите перехватить сенсорный экран в и хотите распространять остальное касание на дочерние представления (X и Y), то переопределите OnTouchListner из X и Y, вернув false для прокрутки и верно для отдыха. И переопределить OnTouchListner из A с помощью салфетки, возвращая true и остальное возвращающее false.

Ответ 5

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

Наконец-то я нашел решение, которое работает. Используя библиотеку RxBinding, вы можете наблюдать все события касания, включая события, которые расходуются путем перекрытия представлений детей.

фрагмент кода Котлин

RxView.touches(view)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(

        object  : Observer<MotionEvent> {
            override fun onCompleted() {
            }

            override fun onNext(t: MotionEvent?) {
                Log.d("motion event onNext $t")
            }

            override fun onError(e: Throwable?) {
            }
        }
    )

См. здесь и здесь для получения более подробной информации.