Обнаружение распространенных жестов

Попробуйте способ создания
Jetpack Compose — рекомендуемый набор инструментов пользовательского интерфейса для Android. Узнайте, как использовать сенсорный ввод и ввод в Compose.

Сенсорный жест возникает, когда пользователь кладет один или несколько пальцев на сенсорный экран, и ваше приложение интерпретирует эту схему касаний как жест. Обнаружение жестов состоит из двух этапов:

  1. Сбор данных о событиях касания.
  2. Интерпретация данных, чтобы определить, соответствуют ли они критериям жестов, поддерживаемых вашим приложением.

Классы AndroidX

В примерах в этом документе используются классы GestureDetectorCompat и MotionEventCompat . Эти классы находятся в библиотеке AndroidX . По возможности используйте классы AndroidX, чтобы обеспечить совместимость с более ранними устройствами. MotionEventCompat не является заменой класса MotionEvent . Скорее, он предоставляет статические служебные методы, которым вы передаете объект MotionEvent для получения действия, связанного с этим событием.

Сбор данных

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

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

Захват событий касания для действия или представления

Чтобы перехватить события касания в Activity или View , переопределите обратный вызов onTouchEvent() .

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

Котлин

class MainActivity : Activity() {
    ...
    // This example shows an Activity. You can use the same approach if you are 
    // subclassing a View.
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(DEBUG_TAG, "Action was DOWN")
                true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(DEBUG_TAG, "Action was MOVE")
                true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(DEBUG_TAG, "Action was UP")
                true
            }
            MotionEvent.ACTION_CANCEL -> {
                Log.d(DEBUG_TAG, "Action was CANCEL")
                true
            }
            MotionEvent.ACTION_OUTSIDE -> {
                Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                true
            }
            else -> super.onTouchEvent(event)
        }
    }
}

Ява

public class MainActivity extends Activity {
...
// This example shows an Activity. You can use the same approach if you are
// subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()) {
        case (MotionEvent.ACTION_DOWN) :
            Log.d(DEBUG_TAG,"Action was DOWN");
            return true;
        case (MotionEvent.ACTION_MOVE) :
            Log.d(DEBUG_TAG,"Action was MOVE");
            return true;
        case (MotionEvent.ACTION_UP) :
            Log.d(DEBUG_TAG,"Action was UP");
            return true;
        case (MotionEvent.ACTION_CANCEL) :
            Log.d(DEBUG_TAG,"Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE) :
            Log.d(DEBUG_TAG,"Movement occurred outside bounds of current screen element");
            return true;
        default :
            return super.onTouchEvent(event);
    }
}

Этот код создает в Logcat сообщения, подобные приведенным ниже, когда пользователь нажимает, касается, удерживает и перетаскивает:

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

Для пользовательских жестов вы можете затем выполнить собственную обработку этих событий, чтобы определить, представляют ли они жест, который вам нужно обработать. Однако если ваше приложение использует распространенные жесты, такие как двойное касание, касание и удержание, переброс и т. д., вы можете воспользоваться преимуществами класса GestureDetector . GestureDetector упрощает обнаружение распространенных жестов без самостоятельной обработки отдельных событий касания. Это обсуждается далее в разделе «Обнаружение жестов» .

Захват событий касания для одного просмотра

В качестве альтернативы onTouchEvent() вы можете прикрепить объект View.OnTouchListener к любому объекту View с помощью метода setOnTouchListener() . Это позволяет прослушивать события касания без создания подкласса существующего View , как показано в следующем примере:

Котлин

findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
    // Respond to touch events.
    true
}

Ява

View myView = findViewById(R.id.my_view);
myView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        // Respond to touch events.
        return true;
    }
});

Остерегайтесь создания прослушивателя, который возвращает false для события ACTION_DOWN . Если вы это сделаете, прослушиватель не будет вызываться для последующей последовательности событий ACTION_MOVE и ACTION_UP . Это связано с тем, что ACTION_DOWN является отправной точкой для всех событий касания.

Если вы создаете собственное представление, вы можете переопределить onTouchEvent() , как описано ранее.

Обнаружение жестов

Android предоставляет класс GestureDetector для обнаружения распространенных жестов. Некоторые из поддерживаемых жестов включают onDown() , onLongPress() и onFling() . Вы можете использовать GestureDetector в сочетании с описанным ранее методом onTouchEvent() .

Обнаружение всех поддерживаемых жестов

Когда вы создаете экземпляр объекта GestureDetectorCompat , одним из принимаемых им параметров является класс, реализующий интерфейс GestureDetector.OnGestureListener . GestureDetector.OnGestureListener уведомляет пользователей о возникновении определенного события касания. Чтобы ваш объект GestureDetector мог получать события, переопределите метод представления или действия onTouchEvent() и передайте все наблюдаемые события экземпляру детектора.

В следующем фрагменте кода возвращаемое значение true от отдельных методов on <TouchEvent> указывает на то, что событие касания обработано. Возвращаемое значение false передает события через стек представлений до тех пор, пока касание не будет успешно обработано.

Если вы запустите следующий фрагмент в тестовом приложении, вы сможете понять, как запускаются действия при взаимодействии с сенсорным экраном и каково содержимое MotionEvent для каждого события касания. Затем вы увидите, сколько данных генерируется для простых взаимодействий.

Котлин

private const val DEBUG_TAG = "Gestures"

class MainActivity :
        Activity(),
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    private lateinit var mDetector: GestureDetectorCompat

    // Called when the activity is first created.
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = GestureDetectorCompat(this, this)
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (mDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDown: $event")
        return true
    }

    override fun onFling(
            event1: MotionEvent,
            event2: MotionEvent,
            velocityX: Float,
            velocityY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onFling: $event1 $event2")
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onLongPress: $event")
    }

    override fun onScroll(
            event1: MotionEvent,
            event2: MotionEvent,
            distanceX: Float,
            distanceY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onShowPress: $event")
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapUp: $event")
        return true
    }

    override fun onDoubleTap(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTap: $event")
        return true
    }

    override fun onDoubleTapEvent(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
        return true
    }

    override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
        return true
    }

}

Ява

public class MainActivity extends Activity implements
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener{

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetectorCompat mDetector;

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = new GestureDetectorCompat(this,this);
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
            float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

Обнаружение подмножества поддерживаемых жестов

Если вы хотите обработать только несколько жестов, вы можете расширить GestureDetector.SimpleOnGestureListener вместо реализации интерфейса GestureDetector.OnGestureListener .

GestureDetector.SimpleOnGestureListener обеспечивает реализацию всех методов on <TouchEvent> , возвращая false для всех из них. Это позволяет переопределить только те методы, которые вам интересны. Например, следующий фрагмент кода создает класс, который расширяет GestureDetector.SimpleOnGestureListener и переопределяет onFling() и onDown() .

Независимо от того, используете ли вы GestureDetector.OnGestureListener или GestureDetector.SimpleOnGestureListener , рекомендуется реализовать метод onDown() , который возвращает true . Это связано с тем, что все жесты начинаются с сообщения onDown() . Если вы возвращаете false из onDown() , как это делает GestureDetector.SimpleOnGestureListener по умолчанию, система предполагает, что вы хотите игнорировать остальную часть жеста, и другие методы GestureDetector.OnGestureListener не вызываются. Это может вызвать непредвиденные проблемы в вашем приложении. Возвращайте false из onDown() только в том случае, если вы действительно хотите игнорировать весь жест.

Котлин

private const val DEBUG_TAG = "Gestures"

class MainActivity : Activity() {

    private lateinit var mDetector: GestureDetectorCompat

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mDetector = GestureDetectorCompat(this, MyGestureListener())
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        mDetector.onTouchEvent(event)
        return super.onTouchEvent(event)
    }

    private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }
    }
}

Ява

public class MainActivity extends Activity {

    private GestureDetectorCompat mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDetector = new GestureDetectorCompat(this, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
              return true;
        }
        return super.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final String DEBUG_TAG = "Gestures";

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }
    }
}

Дополнительные ресурсы