Résolvez le conflit glissant lorsque RecyclerView vertical imbrique RecyclerView horizontal

Description du problème

Nous écrivons un flux en cascade, si le RecyclerView vertical imbrique le RecyclerView horizontal, lorsque le RecyclerView horizontal est glissé, le RecyclerView vertical gigue.

Résumé de la distribution des événements

expéditionTouchEvent

return true: indique que tous les événements ont été digérés dans la vue
return false: indique que l'événement ne sera plus distribué dans cette couche, et sera remis à la méthode onTouchEvent du contrôle supérieur de consommation
return super.dispatchTouchEvent (ev): l'événement par défaut sera distribué à cette couche Interception d'événement de couche méthode onInterceptTouchEvent pour le traitement

onInterceptTouchEvent

return true: signifie intercepter l'événement, et remettre l'événement intercepté au onTouchEvent du contrôle de cette couche pour traitement
return false: signifie ne pas intercepter l'événement et l'événement peut être distribué avec succès à l'enfant View
return super.onInterceptTouchEvent (ev): default signifie non Intercepter l'événement et transmettre l'événement au dispatchTouchEvent de la couche suivante de View

onTouchEvent

return true: indique que l'événement a été consommé après que onTouchEvent a traité l'événement
return fasle: indique que l'événement n'est pas répondu, alors l'événement continuera à être passé à la méthode onTouchEvent de la vue supérieure jusqu'à ce que la méthode onTouchEvent d'une vue renvoie vrai
retour super.dispatchTouchEvent ( ev): indique que l'événement ne répond pas, et le résultat est le même que return false

analyse du problème

Lorsque vous faites glisser le RecyclerView horizontal, l'événement est transmis depuis le RecyclerView vertical. Lorsque notre mouvement de glissement déclenche l'événement de glissement RecyclerView vertical, l'événement sera intercepté, de sorte que le RecyclerView horizontal ne glisse pas, tandis que le vertical Le RecyclerView va trembler de haut en bas.

RecyclerView déclencheur glissant une partie du code source 

public boolean onInterceptTouchEvent (MotionEvent e) {         if (this.mLayoutFrozen) {             return false;         } else if (this.dispatchOnItemTouchIntercept (e)) {             this.cancelTouch ();             retourne vrai;         } else if (this.mLayout == null) {             return false;         } else {             booléen canScrollHorizontally = this.mLayout.canScrollHorizontally ();             booléen canScrollVertically = this.mLayout.canScrollVertically ();             if (this.mVelocityTracker == null) {                 this.mVelocityTracker = VelocityTracker.obtain ();             }












            this.mVelocityTracker.addMovement (e);
            int action = e.getActionMasked ();
            int actionIndex = e.getActionIndex ();
            switch (action) {             case 0:                ...             case 1:                ...             // 从 这里 开始             case 2: // 这里 的 2 为 ACTION_MOVE = 2                 int index = e.findPointerIndex (this.mScrollPointerId);                 if (index <0) {                     Log.e ("RecyclerView", "Erreur lors du traitement du défilement; index du pointeur pour l'id" + this.mScrollPointerId + "introuvable. Des MotionEvents ont-ils été ignorés?");                     retourner faux;                 }










                int x = (int) (e.getX (index) + 0,5F);
                int y = (int) (e.getY (index) + 0.5F);
                if (this.mScrollState! = 1) {                     int dx = x - this.mInitialTouchX;                     int dy = y - this.mInitialTouchY;                     booléen startScroll = false;                     if (canScrollHorizontally && Math.abs (dx)> this.mTouchSlop) {                         this.mLastTouchX = x;                         startScroll = vrai;                     }






                    if (canScrollVertically && Math.abs (dy)> this.mTouchSlop) {                         this.mLastTouchY = y;                         startScroll = vrai;                     }


                    if (startScroll) {this.setScrollState                         (1);                     }                 }                 break;             //             Termine ici             cas 3:                ...             } return this.mScrollState == 1;         }     }










En regardant le code source de RecyclerView ci-dessus, nous pouvons voir que lorsque

if (canScrollHorizontally && Math.abs (dx)> this.mTouchSlop) {                         this.mLastTouchX = x;                         startScroll = vrai;                     }


if (canScrollVertically && Math.abs (dy)> this.mTouchSlop) {                         this.mLastTouchY = y;                         startScroll = vrai;                     }


Lorsque ces deux conditions sont remplies, startScroll prend la valeur true, puis appelle this.setScrollState (1);


void setScrollState (int state) {         if (state! = this.mScrollState) {// mScrollState 默认 值为 0             this.mScrollState = state;             if (état! = 2) {                 this.stopScrollersInternal ();             }




            this.dispatchOnScrollStateChanged (état);
        }
    }

Définissez la valeur par défaut de mScroState sur 1, et finalement onInterceptTouchEvent renvoie

renvoie this.mScrollState == 1;

C'est vrai. Après avoir compris le code source du déclencheur glissant, nous pouvons modifier le RecyclerView ici.
Comment modifier

Jetons un coup d'œil aux conditions qui déclenchent la méthode de glissement RecyclerView

if (canScrollHorizontally && Math.abs (dx)> this.mTouchSlop) {                         this.mLastTouchX = x;                         startScroll = vrai;                     }


if (canScrollVertically && Math.abs (dy)> this.mTouchSlop) {                         this.mLastTouchY = y;                         startScroll = vrai;                     }


Condition 1: Lorsque le glissement horizontal est possible et que la valeur absolue de la distance de glissement horizontal est supérieure au seuil de déclenchement du glissement Déclenchement mTouchSlop
Condition 2: Lorsque le glissement vertical est possible et que la valeur absolue de la distance de glissement vertical est supérieure au seuil de déclenchement du glissement Déclenchement mTouchSlop
Quel est le problème?

Le problème est que tant que la valeur absolue de la distance de glissement est supérieure à la valeur seuil. En combinant notre exemple, tant que la valeur absolue de la composante de distance de glissement verticale reçue par le RecyclerView vertical à l'extérieur est supérieure au seuil mTouchSlop, cela déclenchera la deuxième condition pour retourner true et l'intercepter.
Même si la composante de distance du glissement horizontal de l'utilisateur est supérieure à la verticale, elle ne sera pas transmise au RecyclerView horizontal, de sorte que le problème de gigue RecyclerView vertical se produira.
Comment le résoudre.
Connaissant le problème, il suffit d'ajouter le jugement suivant.

if (canScrollHorizontally && Math.abs (dx)> this.mTouchSlop
&& Math.abs (dx)> Math.abs (dy)) {                         startScroll = true;                     }

if (canScrollVertically && Math.abs (dy)> this.mTouchSlop
&& Math.abs (dy)> Math.abs (dx)) {                         startScroll = true;                     }

Lorsque vous glissez horizontalement, déterminez si la composante horizontale est supérieure à la composante verticale, et vice versa. De cette manière, une séparation par glissement de 45 degrés peut être obtenue. Lorsque l'utilisateur et l'angle horizontal sont inférieurs à 45 degrés, le glissement sera transféré au RecyclerView horizontal pour traitement, et vice versa.

 Le code source est joint comme suit:

package com.newsweekly.livepi.mvp.ui.widget;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

public class BetterRecyclerView extends RecyclerView {

    private int mScrollPointerId;
    private int mInitialTouchX, mInitialTouchY;
    private int mTouchSlop;

    public BetterRecyclerView (@NonNull Context context) {
        super(context);
        init();
    }

    public BetterRecyclerView (@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BetterRecyclerView (@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        ViewConfiguration vc = ViewConfiguration.get(getContext());
        this.mTouchSlop = vc.getScaledTouchSlop();
    }

    @Override
    public void setScrollingTouchSlop(int slopConstant) {
        ViewConfiguration vc = ViewConfiguration.get(this.getContext());
        switch (slopConstant) {
            case 0:
                this.mTouchSlop = vc.getScaledTouchSlop();
            case 1:
                this.mTouchSlop = vc.getScaledPagingTouchSlop();
                break;
            default:
                Log.w("RecyclerView", "setScrollingTouchSlop(): bad argument constant " + slopConstant + "; using default value");

        }
        super.setScrollingTouchSlop(slopConstant);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();
        boolean canScrollVertically = getLayoutManager().canScrollVertically();
        int action = e.getActionMasked();
        int actionIndex = e.getActionIndex();
        switch (action) {
            //ACTION_DOWN
            case 0:
                mScrollPointerId = e.getPointerId(0);
                this.mInitialTouchX = (int) (e.getX() + 0.5F);
                this.mInitialTouchY = (int) (e.getY() + 0.5F);
                return super.onInterceptTouchEvent(e);
            //ACTION_MOVE
            case 2:
                int index = e.findPointerIndex(this.mScrollPointerId);
                if (index < 0) {
                    Log.e("RecyclerView", "Error processing scroll; pointer index for id " + this.mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }

                int x = (int) (e.getX(index) + 0.5F);
                int y = (int) (e.getY(index) + 0.5F);
                if (getScrollState() != 1) {
                    int dx = x - this.mInitialTouchX;
                    int dy = y - this.mInitialTouchY;
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > this.mTouchSlop && Math.abs(dx) > Math.abs(dy)) {
                        startScroll = true;
                    }

                    if (canScrollVertically && Math.abs(dy) > this.mTouchSlop && Math.abs(dy) > Math.abs(dx)) {
                        startScroll = true;
                    }

                    Log.d("MyRecyclerView", "canX:" + canScrollHorizontally + "--canY" + canScrollVertically + "--dx:" + dx + "--dy:" + dy + "--startScorll:" + startScroll + "--mTouchSlop" + mTouchSlop);

                    return startScroll && super.onInterceptTouchEvent(e);
                }
                return super.onInterceptTouchEvent(e);
            //ACTION_POINTER_DOWN
            case 5:
                this.mScrollPointerId = e.getPointerId(actionIndex);
                this.mInitialTouchX = (int) (e.getX(actionIndex) + 0.5F);
                this.mInitialTouchY = (int) (e.getY(actionIndex) + 0.5F);
                return super.onInterceptTouchEvent(e);
        }

        return super.onInterceptTouchEvent(e);

    }
}

Je suppose que tu aimes

Origine blog.csdn.net/xifei66/article/details/105551123
conseillé
Classement