Diseñar una IU es solo una parte de la creación de una vista personalizada. También debes hacer que tu vista responda a la entrada del usuario de manera similar a la acción real que estás imitando.
Haz que los objetos de tu app actúen como lo hacen los objetos reales. Por ejemplo, no permitas que las imágenes de tu app desaparezcan y vuelvan a aparecer en otro lugar, porque los objetos del mundo real no hacen eso. En cambio, mueve las imágenes de un lugar a otro.
Los usuarios pueden percibir un comportamiento sutil o un pequeño cambio en una interfaz y reaccionan mejor a las sutilezas que imitan el mundo real. Por ejemplo, cuando los usuarios arrojan un objeto en la IU, dales una sensación de inercia al principio que retrase el movimiento. Al final del movimiento, dales una sensación de impulso que lleve el objeto más allá del lanzamiento.
En esta página, se muestra cómo usar las funciones del marco de trabajo de Android para agregar estos comportamientos del mundo real a tu vista personalizada.
Puedes encontrar más información relacionada en Descripción general de los eventos de entrada y Descripción general de la animación de propiedades.
Cómo controlar los gestos de entrada
Al igual que muchos otros marcos de trabajo de la IU, Android admite un modelo de evento de entrada. Las acciones del usuario se convierten en eventos que desencadenan devoluciones de llamada, y puedes anular las devoluciones de llamada para personalizar cómo responde tu app al usuario. El evento de entrada más común
en el sistema Android es el táctil, que activa
onTouchEvent(android.view.MotionEvent).
Anula este método para controlar el evento de la siguiente manera:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
Los eventos táctiles por sí mismos no son particularmente útiles. Las IU táctiles modernas definen las interacciones en términos de gestos, como presionar, tirar, empujar, arrojar y hacer zoom. Para convertir eventos táctiles sin procesar en gestos, Android proporciona GestureDetector.
Para construir un GestureDetector, pasa una instancia de una clase
que implemente
GestureDetector.OnGestureListener.
Si solo quieres procesar algunos gestos, puedes extender GestureDetector.SimpleOnGestureListener en lugar de implementar la interfaz GestureDetector.OnGestureListener. Por ejemplo, este código crea una clase que extiende GestureDetector.SimpleOnGestureListener y anula 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());
Ya sea que uses GestureDetector.SimpleOnGestureListener o no, siempre implementa un método onDown() que muestre true. Este paso es necesario porque todos los gestos comienzan con un mensaje onDown(). Si muestras false desde onDown(), como lo hace GestureDetector.SimpleOnGestureListener, el sistema supone que quieres ignorar el resto del gesto y no se llama a los otros métodos de GestureDetector.OnGestureListener. Solo muestra false desde onDown() si quieres ignorar un gesto completo.
Una vez que hayas implementado GestureDetector.OnGestureListener y creado
una instancia de GestureDetector, podrás usar tu
GestureDetector para interpretar los eventos táctiles que recibas en
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; }
Cuando pasas a onTouchEvent() un evento táctil que este elemento no reconoce como parte de un gesto, se muestra false. Luego, puedes ejecutar tu propio código de detección de gestos personalizado.
Cómo crear movimientos físicamente posibles
Los gestos son una forma eficaz de controlar los dispositivos con pantalla táctil, pero pueden ser contradictorios y difíciles de recordar, a menos que produzcan resultados físicamente posibles.
Por ejemplo, supongamos que quieres implementar un gesto de deslizamiento horizontal que haga que el elemento dibujado en la vista gire alrededor de su eje vertical. Este gesto tiene sentido si la IU responde con movimiento rápido en la dirección del lanzamiento y, luego, disminuye la velocidad, como si el usuario empujara un volante y lo hiciera girar.
En la documentación sobre cómo
animar un gesto de desplazamiento, se proporciona una explicación detallada sobre cómo implementar tu propio comportamiento de desplazamiento. Sin embargo, simular la sensación de un volante no es trivial. Se requiere mucha física y matemática para que un modelo de volante funcione de manera correcta. Afortunadamente, Android proporciona clases de ayuda para simular este comportamiento y otros. La clase Scroller es la base para controlar los gestos de lanzamiento de tipo volante.
Para iniciar un lanzamiento, llama a fling() con la velocidad de inicio y los valores de x y de y mínimos y máximos del lanzamiento. Para el valor de velocidad, puedes usar el valor calculado por 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; }
La llamada a fling() configura el modelo de física para el gesto de lanzamiento. Luego, actualiza el Scroller llamando a
Scroller.computeScrollOffset()
a intervalos regulares. computeScrollOffset() actualiza el estado interno del objeto Scroller leyendo la hora actual y utilizando el modelo de física para calcular la posición de x y de y en ese momento. Llama a getCurrX() y getCurrY() para recuperar estos valores.
La mayoría de las vistas pasan las posiciones x y y del objeto Scroller directamente a scrollTo().
Este ejemplo es un poco diferente: usa la posición de x de desplazamiento actual para establecer el ángulo de rotación de la vista.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
La clase Scroller calcula las posiciones de desplazamiento, pero no aplica automáticamente esas posiciones a tu vista. Aplica nuevas coordenadas con la frecuencia suficiente para que la animación de desplazamiento se vea uniforme. Hay dos formas de hacerlo:
- Llama a
postInvalidate()después de llamar afling()para forzar un nuevo diseño. Esta técnica requiere que calcules los desplazamientos enonDraw()y llames apostInvalidate()cada vez que cambie el desplazamiento. - Configura un
ValueAnimatorpara proporcionar una animación durante la duración del desplazamiento y agrega un objeto de escucha para procesar actualizaciones de la animación llamando aaddUpdateListener(). Esta técnica te permite animar propiedades de unView.
Cómo hacer que tus transiciones sean naturales
Los usuarios esperan que una IU moderna haga una transición natural de un estado a otro: los elementos de la IU deben desvanecerse, en lugar de aparecer y desaparecer, y los movimientos deben comenzar y terminar de forma natural, en lugar de iniciarse y detenerse de forma abrupta. El marco de trabajo de la animación de propiedades de Android facilita las transiciones naturales.
Para usar el sistema de animación, cuando una propiedad cambia lo que afecta la apariencia de tu vista, no cambies la propiedad directamente. En cambio, usa ValueAnimator para realizar el cambio. En el siguiente ejemplo, cuando se modifica el componente secundario seleccionado en la vista, la vista renderizada completa gira para que el puntero de selección se centre.
ValueAnimator cambia la rotación durante un período de varios cientos de milisegundos en lugar de establecer de inmediato el nuevo valor de rotación.
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();
Si el valor que quieres cambiar es una de las propiedades de View
base, hacer la animación es aún más fácil, porque las vistas tienen un
ViewPropertyAnimator
integrado que está optimizado para la animación simultánea de múltiples propiedades, como en el
siguiente ejemplo:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();