Desenhar uma IU é apenas uma parte da criação de uma visualização personalizada. Também é necessário fazer com que a visualização responda à entrada do usuário de uma maneira parecida com a ação real que você está imitando.
Faça com que os objetos no seu app ajam como objetos reais. Por exemplo, não deixe que as imagens no seu app desapareçam e reapareçam em outro lugar, porque os objetos no mundo real não fazem isso. Em vez disso, mova as imagens de um lugar para outro.
Os usuários percebem até mesmo comportamentos ou aparências sutis em uma interface e reagem melhor a sutilezas que imitam o mundo real. Por exemplo, quando os usuários deslizam rapidamente um objeto da interface, dê a eles uma sensação de inércia no início que atrasa o movimento. No final do movimento, dê a eles uma sensação de impulso que leve o objeto além da rolagem rápida.
Esta página mostra como usar recursos do framework do Android para adicionar esses comportamentos do mundo real à sua visualização personalizada.
Você pode encontrar mais informações relacionadas em Visão geral dos eventos de entrada e Visão geral da animação de propriedades.
Gerenciar gestos de entrada
Como muitos outras frameworks de IU, o Android é compatível com um modelo de evento de entrada. As ações do usuário se transformam em eventos que acionam callbacks, e é possível modificar os callbacks para personalizar o modo como o app responde ao usuário. O evento de entrada mais comum no sistema Android é o toque, que aciona onTouchEvent(android.view.MotionEvent).
Substitua esse método para processar o evento da seguinte maneira:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
Os eventos de toque por si só não são muito úteis. As UIs de toque modernas definem interações em gestos, como tocar, puxar, empurrar, lançar e aplicar zoom. Para converter eventos de toque brutos em gestos, o Android
fornece
GestureDetector.
Crie um GestureDetector transmitindo uma instância de uma classe que implementa GestureDetector.OnGestureListener.
Se você quiser processar apenas alguns gestos, estenda
GestureDetector.SimpleOnGestureListener
em vez de implementar a interface GestureDetector.OnGestureListener. Por exemplo, este código cria uma classe que estende
GestureDetector.SimpleOnGestureListener e modifica
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());
Se você usa ou não GestureDetector.SimpleOnGestureListener,
implemente sempre um
método onDown()
que retorna true. Isso é necessário porque todos os gestos
começam com uma mensagem onDown(). Se você retornar false
de onDown(), como
GestureDetector.SimpleOnGestureListener faz, o sistema presumirá
que você quer ignorar o restante do gesto, e os outros métodos de
GestureDetector.OnGestureListener não serão chamados. Só retorne
false de onDown() se quiser ignorar um gesto
inteiro.
Depois de implementar GestureDetector.OnGestureListener e criar
uma instância de GestureDetector, você poderá usar seu
GestureDetector para interpretar os eventos de toque recebidos em
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; }
Quando você transmite onTouchEvent() a um evento de toque que ele não reconhece como parte de um gesto, ele retorna false. Você pode, então, executar seu próprio código personalizado de detecção de gestos.
Criar movimento fisicamente plausível
Os gestos são uma maneira poderosa de controlar dispositivos touchscreen, mas podem ser pouco intuitivos e difíceis de lembrar, a menos que produzam resultados fisicamente plausíveis.
Por exemplo, suponha que você queira implementar um gesto de deslizar horizontal que faça o item desenhado na visualização girar em torno do eixo vertical. Esse gesto faz sentido se a interface responde movendo-se rapidamente na direção do deslizar rapidamente, depois diminuindo a velocidade, como se o usuário tivesse girado uma roda.
A documentação sobre como
animar um gesto
de rolagem explica detalhadamente como implementar seu próprio comportamento
de rolagem. No entanto, simular a sensação de uma roda não é fácil. É necessário usar muita física e matemática para que o modelo da roda funcione corretamente. Felizmente, o Android oferece classes auxiliares para simular esse e outros comportamentos. A classe
Scroller
é a base para lidar com gestos de rolagem rápida no estilo de roda.
Para iniciar um deslizar rapidamente, chame
fling()
com a velocidade inicial e os valores mínimo e máximo de x e y
do deslizar rapidamente. Para o valor da velocidade, você pode usar o 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; }
A chamada para fling() configura o modelo de física para o gesto de rolagem rápida. Depois, atualize o Scroller chamando Scroller.computeScrollOffset() em intervalos regulares. computeScrollOffset() atualiza o estado interno do objeto Scroller lendo o horário atual e usando o modelo de física para calcular a posição x e y naquele momento. Chame
getCurrX()
e
getCurrY()
para recuperar esses valores.
A maioria das visualizações transmite as posições x e y do objeto Scroller diretamente para scrollTo().
Este exemplo é um pouco diferente: ele usa a posição atual de rolagem x para definir o ângulo de rotação da visualização.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
A classe Scroller calcula as posições de rolagem, mas não as aplica automaticamente à visualização. Aplique novas coordenadas com frequência suficiente para tornar a animação de rolagem suave. Há duas maneiras de fazer isso:
- Force uma nova renderização chamando
postInvalidate()depois de chamarfling(). Essa técnica exige que você calcule os deslocamentos de rolagem emonDraw()e chamepostInvalidate()sempre que o deslocamento de rolagem for alterado. - Configure um
ValueAnimatorpara animar a duração da rolagem rápida e adicione um listener para processar atualizações de animação chamandoaddUpdateListener(). Essa técnica permite animar propriedades de umView.
Tornar suas transições suaves
Os usuários esperam que uma interface moderna faça transições entre estados de modo suave: elementos da interface esmaecendo, em vez de aparecer e desaparecer, e movimentos começando e terminando com leveza, em vez de começar e parar abruptamente. O framework de animação de propriedade do Android facilita as transições suaves.
Para usar o sistema de animação, sempre que uma propriedade mudar e afetar a aparência da sua visualização, não a altere diretamente. Em vez disso, use
ValueAnimator para fazer a mudança. No exemplo a seguir, modificar o componente filho selecionado na visualização faz com que toda a visualização renderizada gire para que o ponteiro de seleção seja centralizado.
O ValueAnimator muda a rotação durante um período de várias centenas de milissegundos em vez de definir imediatamente o novo valor de rotação.
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();
Se o valor que você quer mudar for uma das propriedades básicas da View, fazer a animação será ainda mais fácil, porque as visualizações têm um ViewPropertyAnimator integrado que é otimizado para animação simultânea de várias propriedades, como no exemplo a seguir:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();