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