Tornar uma visualização personalizada interativa

Testar o Compose
O Jetpack Compose é o kit de ferramentas de interface recomendado para Android. Aprenda a trabalhar com layouts no Compose.

Desenhar uma IU é apenas uma parte da criação de uma visualização personalizada. Você também precisa fazer com que sua visualização responda à entrada do usuário de uma forma parecida com a uma 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 as imagens no seu aplicativo saem da existência e reaparecem em outro lugar, porque os objetos no mundo real não façam isso. Em vez disso, mova as imagens de um lugar para outra.

Os usuários percebem até mesmo comportamentos sutis ou sentem em uma interface e reagem melhor a sutilezas que imitam o mundo real. Por exemplo, quando os usuários arrastam um objeto da interface, dão a eles uma sensação de inércia no início que atrasa o movimento. No fim do movimento, dão a elas uma sensação de impulso que carrega o objeto além do deslizamento rápido.

Esta página demonstra como usar recursos do framework do Android para adicionar esses comportamentos reais à sua visualização personalizada.

Você pode encontrar informações adicionais relacionadas em Visão geral dos eventos de entrada e Animação de propriedades geral.

Processar gestos de entrada

Como muitos outras frameworks de IU, o Android é compatível com um modelo de evento de entrada. Usuário se transformam em eventos que acionam callbacks, e você pode substituir para personalizar a resposta do app ao usuário. A 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. Interfaces de toque modernas definir interações em termos de gestos, como tocar, puxar, empurrar, movimentar 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 quiser processar apenas alguns gestos, você pode estender GestureDetector.SimpleOnGestureListener em vez de implementar o GestureDetector.OnGestureListener interface gráfica do usuário. Por exemplo, esse código cria uma classe que estende GestureDetector.SimpleOnGestureListener e substituições 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());

Quer você use ou não GestureDetector.SimpleOnGestureListener, sempre implemente uma onDown() que retorna true. Isso é necessário porque todos os gestos comece com uma mensagem onDown(). Se você devolver false de onDown(), conforme GestureDetector.SimpleOnGestureListener faz, o sistema presume que deve ignorar o resto do gesto, e os outros métodos de GestureDetector.OnGestureListener não sejam chamadas. Só devolver false de onDown() se você quiser ignorar uma mensagem inteira. gesto.

Depois de implementar GestureDetector.OnGestureListener e criar uma instância de GestureDetector, é possível usar GestureDetector para interpretar os eventos de toque recebidos no 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 reconhecer como parte de um gesto, ela 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 contraintuitiva e difícil de lembrar, a menos que produza resultados plausíveis.

Por exemplo, suponha que você queira implementar um gesto de deslize rápido horizontal que define o item desenhado na visualização girando em torno de seu eixo vertical. Este gesto faz sentido se a interface responde movendo-se rapidamente na direção da rolagem, e, em seguida, desacelerar, como se o usuário empurrasse uma roda e fizesse girar.

A documentação sobre como animar uma rolagem gesto fornece uma explicação detalhada sobre como implementar sua própria atividade do seu modelo. Mas simular a sensação de um volante não é trivial. Muita física e matemática é necessária para fazer um modelo de volante funcionar corretamente. Felizmente, O Android oferece classes auxiliares para simular esse e outros comportamentos. A Scroller é a base para lidar com gestos de rolagem rápida no estilo de volante.

Para iniciar uma rolagem rápida, chame fling() com a velocidade inicial e os valores mínimos e máximos x e y valores da rolagem rápida. Para o valor da velocidade, você pode usar o valor calculado pela 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 a rolagem gesto. Em seguida, atualize o Scroller chamando Scroller.computeScrollOffset() em intervalos regulares. computeScrollOffset() atualiza o estado interno do objeto Scroller lendo a hora atual e usando o modelo de física para calcular as posições x e y tempo de resposta. Ligação getCurrX() e getCurrY() para recuperar esses valores.

A maioria das visualizações transmite os valores x e y do objeto Scroller. posições diretamente scrollTo(). Este 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 aplica essas posições automaticamente à sua visualização. Aplicar novas coordenadas com frequência suficiente para fazer com que a animação de rolagem pareça suave. Há duas maneiras de faça isto:

  • Force um novo desenho chamando postInvalidate() depois de chamar fling(). Essa técnica exige que você computar deslocamentos de rolagem em onDraw() e chamar postInvalidate() sempre que o deslocamento de rolagem mudanças.
  • Configure um ValueAnimator para animar a duração do deslize rápido e adicionar um listener para processar é atualizada chamando addUpdateListener(). Essa técnica permite animar propriedades de um View:

Suavizar as transições

Os usuários esperam que uma interface moderna faça a transição entre estados com facilidade: elementos da interface aparecem e desaparecem, e os movimentos começam a aparecer e terminando de maneira suave, em vez de começar e parar abruptamente. O Android animação de propriedade framework, que facilita as transições.

Para usar o sistema de animação, sempre que uma propriedade alterar o que afeta seu da visualização, não altere a propriedade diretamente. Em vez disso, use ValueAnimator para fazer a mudança. No exemplo a seguir, a modificação do componente filho selecionado na visualização faz com que todo o gire a visualização para que o ponteiro de seleção fique centralizado. ValueAnimator muda a rotação ao longo de um período de várias centenas milésimos de segundo, 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 um dos View de base propriedades, fazer a animação é ainda mais fácil, porque as visualizações têm uma função ViewPropertyAnimator 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();