Efeito de rolagem

Em dispositivos com o Android 12 e versões mais recentes, o comportamento visual dos eventos de rolagem mudou.

No Android 11 e versões anteriores, um evento de rolagem esticada faz com que os elementos visuais tenham um brilho. No Android 12 e versões mais recentes, os elementos visuais se esticam e voltam ao normal em um evento de arrastar, e deslizam e voltam ao normal em um evento de deslizamento rápido:

Novos comportamentos de rolagem afetam as animações de arrastar e deslizar rapidamente.

O comportamento se aplica a todos os apps que usam o EdgeEffect e a todo o conteúdo que esteja dentro das classes a seguir:

O efeito visual funciona para rolagem vertical e horizontal. Por ser aplicável por padrão a todos os apps que não desativam a rolagem, ele fornece uma experiência de IU mais consistente aos usuários.

Práticas recomendadas

Para garantir que a nova experiência de rolagem funcione bem com o app, siga estas práticas recomendadas:

  • Dimensione corretamente os contêineres de rolagem de modo que as visualizações filhas se encaixem nos próprios limites.
  • Use valores positivos para deltaDistance no método EdgeEffect.onPull(deltaDistance, displacement) ao adicionar valores de esticamento para a rolagem e valores negativos ao reduzir o esticamento.
  • Pare a animação quando um toque for detectado.

Esticar o uso do EdgeEffect

O EdgeEffect adiciona duas APIs para implementar o efeito de rolagem esticada.

float getDistance()
float onPullDistance(float deltaDistance, float displacement)

Para oferecer a melhor experiência ao usuário com a rolagem esticada, faça o seguinte:

  • Quando o usuário soltar e tocar no conteúdo durante a animação de soltar, registre o toque como uma "captura". O usuário interrompe a animação e começa a manipular o esticamento novamente.
  • Quando o usuário mover o dedo na direção oposta do esticamento, interrompa a animação até que o efeito volte ao normal e depois comece a rolar a tela.
  • Quando o usuário desliza rapidamente ao esticar, deslize rapidamente o EdgeEffect para melhorar o efeito do esticamento.

Capturar a animação

Quando um usuário capturar uma animação de esticamento ativa, o método EdgeEffect.isFinished() retornará false. Isso indica que o trecho precisa ser manipulado pelo movimento do toque. Na maioria dos contêineres, isso é detectado no método onInterceptTouchEvent(), conforme mostrado no snippet de código a seguir:

Kotlin

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  ...
  when (action and MotionEvent.ACTION_MASK) {
    MotionEvent.ACTION_DOWN ->
      ...
      isBeingDragged = !edgeEffectBottom.isFinished() ||
          !edgeEffectTop.isFinished()
      ...
  }
  return isBeingDragged
}

Java

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  ...
  switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
      ...
      mIsBeingDragged = !mEdgeEffectBottom.isFinished()
          || !mEdgeEffectTop.isFinished();
      ...

No exemplo anterior, o método onInterceptTouchEvent() retorna true quando mIsBeingDragged é true. Portanto, ele é suficiente para consumir o evento antes que o elemento filho tenha tempo de consumi-lo.

Soltar o efeito de rolagem

É importante soltar o efeito de esticamento antes de iniciar a rolagem para evitar que ele seja aplicado ao conteúdo. O exemplo de código a seguir mostra isso:

Kotlin

override fun onTouchEvent(ev: MotionEvent): Boolean {
  val activePointerIndex = ev.actionIndex

  when (ev.getActionMasked()) {
    MotionEvent.ACTION_MOVE ->
      val x = ev.getX(activePointerIndex)
      val y = ev.getY(activePointerIndex)
      var deltaY = y - mLastMotionY
      val pullDistance = deltaY / height
      val displacement = x / width

      if (deltaY < 0f && mEdgeEffectTop.distance > 0f) {
        deltaY -= height * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0f && mEdgeEffectBottom.distance > 0f) {
        deltaY += height * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }

      ...
  }

Java

@Override
public boolean onTouchEvent(MotionEvent ev) {

  final int actionMasked = ev.getActionMasked();

  switch (actionMasked) {
    case MotionEvent.ACTION_MOVE:
      final float x = ev.getX(activePointerIndex);
      final float y = ev.getY(activePointerIndex);
      float deltaY = y - mLastMotionY;
      float pullDistance = deltaY / getHeight();
      float displacement = x / getWidth();

      if (deltaY < 0 && mEdgeEffectTop.getDistance() > 0) {
        deltaY -= getHeight() * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0 && mEdgeEffectBottom.getDistance() > 0) {
        deltaY += getHeight() * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }
            ...

Ao arrastar, antes de passar o evento de toque para a rolagem aninhada ou arrastar a rolagem, a distância de pull do EdgeEffect precisa ser consumida. No exemplo de código anterior, getDistance() retorna um valor positivo quando um efeito de borda está sendo exibido e pode ser liberado com um movimento. Quando o evento de toque libera o esticamento, ele é consumido primeiro pelo EdgeEffect para que seja liberado completamente antes que outros efeitos, como o da rolagem aninhada, sejam exibidos. Você pode usar o método getDistance() para saber a distância necessária para soltar o efeito atual.

onPullDistance() é diferente de onPull() porque retorna a quantidade consumida do delta transmitido. Anteriormente, o onPull() permitia transmitir valores negativos para a distância total de efeitos de brilho. No Android 12 e versões mais recentes, se onPull() ou onPullDistance() transmitirem valores negativos de deltaDistance quando getDistance() for 0, não haverá mudança no esticamento.

Desativar

Você pode desativar a rolagem no arquivo de layout XML ou programaticamente. O código XML a seguir mostra o android:overScrollMode definido no arquivo de layout.

<!-- Via markup -->
<ScrollView
  ...
  android:overScrollMode="never"
  ...
>

Faça a desativação programaticamente, conforme mostrado no seguinte snippet de código:

Kotlin

<!-- Programmatically-->
...
recyclerview.overScrollMode = View.OVER_SCROLL_NEVER
...

Java

<!-- Programmatically-->
...
recyclerview.setOverScrollMode(View.OVER_SCROLL_NEVER);
...

Enviar feedback

Seu feedback é importante para nós. Avise se você descobrir problemas ou tiver ideias para melhorar este recurso. Veja os problemas existentes antes de criar um novo. Adicione seu voto a um problema existente clicando no botão de estrela.

Criar novo problema

Consulte a documentação do Issue Tracker para saber mais.