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

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

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

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

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

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

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

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

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

Котлин

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

Ява

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

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

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

Котлин

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

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

Ява

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() .

Котлин

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

Ява

@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 .

Котлин

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
}

Ява

@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 для установки угла поворота представления.

Котлин

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

Ява

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

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

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

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

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

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

Котлин

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

Ява

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

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

Котлин

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

Ява

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