web-dev-qa-db-fra.com

Empêcher RecyclerView de consommer des événements tactiles

Problème

J'essaie de simuler le comportement exact de l'application Google Play.

Actuellement, lorsque vous faites défiler une catégorie comme "Top Games" et que vous touchez la liste, cette cellule ainsi que le RecyclerView gère l'événement tactile, vous pouvez dire qu'ils le gèrent tous les deux en raison de l'ondulation apparaissant lorsque vous maintenez votre doigt vers le bas ainsi que le défilement s'arrêtant. Ou si vous cliquez sur une cellule alors que la liste est toujours en mouvement, le contenu est ouvert immédiatement.

Le comportement par défaut est que le défilement s'arrête instantanément si vous placez votre doigt vers le bas pendant qu'il se déplace, et qu'il consomme ensuite cet événement tactile, ce qui signifie que la cellule n'est jamais informée de ce toucher. Cela est extrêmement irritant lorsque le contenu principal de vos applications se trouve dans un RecyclerView et si l'utilisateur fait continuellement défiler puis clique sur une cellule, il lui faut cliquer deux fois, car le premier clic a été consommé en arrêtant le défilement, même si la liste bougeait à peine.

Ce que j'ai essayé jusqu'à présent

Au début, je pensais qu'il devait y avoir une sorte de drapeau à l'intérieur du RecyclerView qui lui dit de ne pas consommer l'événement tactile. J'ai parcouru le code source Android et parcouru la documentation Android en vain).

Ensuite, j'ai essayé de capturer l'événement tactile avec onInterceptTouchEvent et de dire manuellement à ma vue qu'il est touché;

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    View myCell = recyclerView.findChildViewUnder(event.getX(), event.getY());
    myCell.dispatchTouchEvent(event);
    return super.onInterceptTouchEvent(event);
}

Ici, dispatchTouchEvent ne semble rien faire même si la vue que je reçois a la balise correspondante que j'ai définie dans l'adaptateur.

J'ai essayé de résoudre ce problème avec ma mitrailleuse à code sous tous les angles; jouer avec requestDisallowInterceptTouchEvent, appeler manuellement onTouch et remplacer toutes sortes d'écouteurs pour RecyclerView et au-delà.

Rien ne semble fonctionner. Je suis sûr qu'il doit y avoir une solution élégante à cela et j'ai survolé un détail important quelque part sur le chemin de la folie? Surtout parce que les propres applications de Google se comportent de cette manière.

Aidez-moi! :) Merci.

La solution

Grâce à Jagoan Neon, une solution a été trouvée. J'ai fini par envelopper sa réponse dans un RecyclerView personnalisé pour en faciliter l'utilisation.

public class MultiClickRecyclerView extends RecyclerView {
   public MultiClickRecyclerView(Context context) {
        super(context);
   }

   public MultiClickRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
   }

   public MultiClickRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
   }

   @Override
   public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN && this.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            this.stopScroll();
        }
        return super.onInterceptTouchEvent(event);
   }
}
20
vguzzi

Tout d'abord, vous devez définir un OnItemTouchListener, peut-être comme une variable globale comme celle-ci:

RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            Log.d(TAG, "onInterceptTouchEvent: click performed");
            rv.findChildViewUnder(e.getX(), e.getY()).performClick();
            return true;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
};

Pourquoi ACTION_DOWN et SCROLL_STATE_SETTLING?

L'événement de mouvement ACTION_DOWN sera déclenché lorsque vous effectuez une touche sur le RecyclerView, et à ce moment précis, RecyclerView change son état de défilement en SCROLL_STATE_SETTLING alors qu'il essaie d'arrêter le défilement. Et c'est exactement là que vous souhaitez effectuer le clic.

Et n'oubliez pas de retourner vrai pour que vous disiez à votre RecyclerView que vous avez consommé le MotionEvent. Sinon, votre performClick () pourrait être appelé plusieurs fois.

Ajoutez-le à votre RecyclerView sur onResume et supprimez-le sur onPause, et vous êtes prêt;)

METTRE À JOUR

Ajoutez un vérificateur nul lorsque vous effectuez findChildViewUnder - il renvoie null lorsque vous ne touchez pas à une cellule spécifique.

View childView = rv.findChildViewUnder(e.getX(), e.getY());
if (childView != null) childView.performClick();

MISE À JOUR 2

Il s'avère que l'arrêt de RecyclerView du défilement suffirait. Faites ceci à la place:

@Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING)
            rv.stopScroll();
        return false;
    }
25
Jagoan Neon