Desenhar uma IU é apenas uma parte da criação de uma visualização personalizada. Você também precisa fazer a visualização responder à entrada do usuário de uma forma 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 permita que as imagens no app deixem de existir e reapareçam em outro lugar, porque objetos no mundo real não fazem isso. Em vez disso, mova as imagens de um lugar para outro.
Os usuários sentem até mesmo comportamentos sutis ou sentem em uma interface e reagem melhor às sutilezas que imitam o mundo real. Por exemplo, quando os usuários lançarem um objeto da interface, dê uma sensação de inércia no início, o que atrasa o movimento. No final do movimento, dê a eles uma sensação de impulso que transporta o objeto para além da deslizamento.
Esta página demonstra como usar os recursos do framework do Android para adicionar esses comportamentos do mundo real à sua visualização personalizada.
Você pode encontrar mais informações relacionadas na Visão geral dos eventos de entrada e na Visão geral da animação de propriedades.
Processar 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 você pode substituí-los
para personalizar a resposta do app 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 termos de gestos, como tocar, puxar, empurrar, atirar e aplicar zoom. Para converter eventos de toque brutos em gestos, o Android
oferece
GestureDetector
.
Crie um GestureDetector
transmitindo uma instância de uma classe
que implementa
GestureDetector.OnGestureListener
.
Se você quer processar apenas alguns gestos, pode estender
GestureDetector.SimpleOnGestureListener
em vez de implementar a interface
GestureDetector.OnGestureListener
. Por exemplo, esse código cria uma classe que estende
GestureDetector.SimpleOnGestureListener
e substitui
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());
Mesmo que você não use GestureDetector.SimpleOnGestureListener
,
sempre implemente um método
onDown()
que retorne true
. Isso é necessário porque todos os gestos
começam com uma mensagem onDown()
. Se você retornar false
de onDown()
, assim como
GestureDetector.SimpleOnGestureListener
, o sistema vai 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 você quiser ignorar um gesto
inteiro.
Depois de implementar GestureDetector.OnGestureListener
e criar
uma instância de GestureDetector
, você pode usar o
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 a onTouchEvent()
um evento de toque que ele não
reconhece como parte de um gesto, ele retorna false
. Em seguida, você pode executar
seu próprio código personalizado de detecção de gestos.
Criar um movimento fisicamente plausível
Os gestos são uma maneira poderosa de controlar dispositivos touchscreen, mas podem ser contra intuitivos e difíceis de lembrar, a menos que produzam resultados fisicamente plausíveis.
Por exemplo, suponha que você queira implementar um gesto de rolagem horizontal que defina o item desenhado na visualização girando em torno do eixo vertical. Esse gesto faz sentido se a interface responde movendo-se rapidamente na direção da rolagem e, em seguida, desacelerando, como se o usuário empurre o volante e o faça girar.
A documentação sobre como
animar um gesto de
rolagem fornece uma explicação detalhada sobre como implementar seu próprio comportamento
de scoll. Mas simular a sensação de um volante não é trivial. É necessário muita física e matemática para fazer um modelo de volante funcionar corretamente. Felizmente,
o Android fornece classes auxiliares para simular esse e outros comportamentos. A classe
Scroller
é a base para processar gestos de rolagem rápida no estilo de roda.
Para iniciar uma rolagem rápida, chame
fling()
com a velocidade inicial e os valores mínimo e máximo x e y
dele. Para o valor da velocidade, use 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
deslizamento rápido. Em seguida, 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 as posições 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()
.
Esse exemplo é um pouco diferente: ele usa a posição de rolagem x atual
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 que a animação de rolagem fique suave. Há duas maneiras de fazer isso:
- Force um redraw chamando
postInvalidate()
depois de chamarfling()
. Essa técnica exige que você calcule os deslocamentos de rolagem emonDraw()
e chamepostInvalidate()
sempre que ele mudar. - Configure um
ValueAnimator
para 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
.
Suavizar as transições
Os usuários esperam que uma interface moderna faça a transição suave entre os estados: os elementos da interface desaparecem em vez de aparecer e desaparecerem e os movimentos que começam e terminam de modo suave, 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 o que afeta a
aparência da sua visualização, não mude a propriedade 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.
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 base View
, fazer a animação será ainda mais fácil, porque as visualizações têm um
ViewPropertyAnimator
integrado
otimizado para animação simultânea de várias propriedades, como no
exemplo abaixo:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();