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

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

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

  1. Сбор данных о событиях касания.
  2. Анализ данных для определения того, соответствуют ли они критериям жестов, поддерживаемых вашим приложением.

Классы AndroidX

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

Соберите данные

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

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

Отслеживайте события касания для Activity или View.

Чтобы перехватывать события касания в 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)
        }
    }
}

Java

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
}

Java

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
    }

}

Java

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
        }
    }
}

Java

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;
        }
    }
}

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