Как сделать пользовательское представление интерактивным

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

Рисование пользовательского интерфейса — это только одна часть создания пользовательского представления. Также необходимо, чтобы представление реагировало на действия пользователя так, как это происходит в реальном мире.

Объекты в приложении должны вести себя так же, как в реальном мире. Например, изображения не должны внезапно исчезать и появляться в другом месте. Вместо этого переместите изображения из одного места в другое.

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

На этой странице показано, как использовать функции фреймворка Android, чтобы добавить эти реалистичные поведения в ваше пользовательское представление.

Дополнительную информацию можно найти в статьях Обзор событий ввода и Обзор анимации свойств.

Обработка жестов ввода

Как и многие другие фреймворки пользовательского интерфейса, Android поддерживает модель событий ввода. Действия пользователя преобразуются в события, которые запускают обратные вызовы. Вы можете переопределить обратные вызовы, чтобы настроить реакцию приложения на действия пользователя. Самое распространенное событие ввода в системе Android – это касание, которое запускает onTouchEvent(android.view.MotionEvent). Переопределите этот метод, чтобы обрабатывать событие, как показано ниже.

Kotlin

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

Java

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

События касания сами по себе не очень полезны. Современные сенсорные интерфейсы определяют взаимодействия в терминах жестов, таких как нажатие, вытягивание, нажатие, бросание и масштабирование. Чтобы преобразовать необработанные события касания в жесты, Android предоставляет класс GestureDetector.

Создайте GestureDetector, передав экземпляр класса, который реализует GestureDetector.OnGestureListener. Если вам нужно обрабатывать только некоторые жесты, вы можете расширить класс GestureDetector.SimpleOnGestureListener, а не реализовывать интерфейс GestureDetector.OnGestureListener. Например, этот код создает класс, который расширяет GestureDetector.SimpleOnGestureListener и переопределяет onDown(MotionEvent).

Kotlin

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

Java

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

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

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

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

Java

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

Когда вы передаете onTouchEvent() событие касания, которое не распознается как часть жеста, возвращается false. Затем вы можете запустить собственный код распознавания жестов.

Создавайте физически правдоподобные движения

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

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

В документации по анимации жеста прокрутки подробно объясняется, как реализовать собственное поведение прокрутки. Но имитировать ощущение маховика непросто. Чтобы модель маховика работала правильно, требуется много физики и математики. К счастью, Android предоставляет вспомогательные классы для симуляции этого и других поведений. Класс Scroller – это основа для обработки жестов прокрутки в стиле маховика.

Чтобы начать бросок, вызовите fling() с начальной скоростью и минимальным и максимальным значениями x и y броска. В качестве значения скорости можно использовать значение, вычисленное с помощью GestureDetector.

Kotlin

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

Java

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

Вызов fling() настраивает физическую модель для жеста пролистывания. Затем обновляйте Scroller, регулярно вызывая Scroller.computeScrollOffset(). computeScrollOffset() обновляет внутреннее состояние объекта Scroller, считывая текущее время и используя физическую модель для расчета положения x и y в этот момент времени. Чтобы получить эти значения, вызовите getCurrX() и getCurrY().

Большинство представлений передают позиции x и y объекта Scroller напрямую в scrollTo(). Этот пример немного отличается: он использует текущую позицию прокрутки x, чтобы установить угол поворота представления.

Kotlin

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

Java

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

Класс Scroller вычисляет позиции прокрутки, но не применяет их к представлению автоматически. Применяйте новые координаты достаточно часто, чтобы анимация прокрутки выглядела плавной. Это можно сделать двумя способами:

  • Принудительно перерисовывайте карту, вызывая postInvalidate() после вызова fling(). Для этого вам нужно вычислить смещение прокрутки в onDraw() и вызывать postInvalidate() каждый раз, когда оно меняется.
  • Настройте ValueAnimator для анимации на время пролистывания и добавьте прослушиватель для обработки обновлений анимации, вызвав addUpdateListener(). Этот метод позволяет анимировать свойства View.

Сделайте переходы плавными

Пользователи ожидают, что в современном интерфейсе переходы между состояниями будут плавными: элементы интерфейса должны появляться и исчезать постепенно, а движения должны начинаться и заканчиваться плавно, а не резко. Фреймворк анимации свойств в Android упрощает создание плавных переходов.

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

Kotlin

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

Java

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

Если вы хотите изменить значение одного из базовых свойств View, сделать анимацию будет ещё проще, поскольку представления имеют встроенный ViewPropertyAnimator, оптимизированный для одновременной анимации нескольких свойств, как в следующем примере:

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();