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

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

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

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

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

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

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

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

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

Котлин

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

Котлин

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

Котлин

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 .

Котлин

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 .

Котлин

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 изменяет поворот в течение нескольких сотен миллисекунд, а не устанавливает новое значение поворота немедленно.

Котлин

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 , оптимизированный для одновременной анимации нескольких свойств, как в следующем примере:

Котлин

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

Java

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