Рисование пользовательского интерфейса — это только одна часть создания пользовательского представления. Также необходимо, чтобы представление реагировало на действия пользователя так, как это происходит в реальном мире.
Объекты в приложении должны вести себя так же, как в реальном мире. Например, изображения не должны внезапно исчезать и появляться в другом месте. Вместо этого переместите изображения из одного места в другое.
Пользователи замечают даже незначительные изменения в поведении интерфейса и лучше всего реагируют на те, которые имитируют реальный мир. Например, когда пользователь перемещает объект интерфейса, дайте ему ощущение инерции в начале, которое задерживает движение. В конце движения дайте им ощущение импульса, который уносит объект за пределы броска.
На этой странице показано, как использовать функции фреймворка 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();