68 votes

Android : Comment détecter la fin d'un défilement ?

J'utilise la méthode onScroll de GestureDetector.SimpleOnGestureListener pour faire défiler un grand bitmap sur un canevas. Lorsque le défilement est terminé, je veux redessiner le bitmap au cas où l'utilisateur voudrait le faire défiler plus loin ... hors du bord du bitmap, mais je ne vois pas comment détecter la fin du défilement (l'utilisateur a retiré son doigt de l'écran).

e2.getAction() semble toujours renvoyer la valeur 2, ce qui ne nous aide pas. e2.getPressure semble renvoyer des valeurs assez constantes (autour de 0,25) jusqu'au dernier appel onScroll où la pression semble tomber à environ 0,13. Je suppose que je pourrais détecter cette réduction de la pression, mais ce sera loin d'être infaillible.

Il doit y avoir un meilleur moyen : quelqu'un peut-il m'aider, s'il vous plaît ?

71voto

Akos Cz Points 3880

Voici comment j'ai résolu le problème. J'espère que cela vous aidera.

// declare class member variables
private GestureDetector mGestureDetector;
private OnTouchListener mGestureListener;
private boolean mIsScrolling = false;

public void initGestureDetection() {
        // Gesture detection
    mGestureDetector = new GestureDetector(new SimpleOnGestureListener() {
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            handleDoubleTap(e);
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            handleSingleTap(e);
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // i'm only scrolling along the X axis
            mIsScrolling = true;                
            handleScroll(Math.round((e2.getX() - e1.getX())));
            return true;
        }

        @Override
        /**
         * Don't know why but we need to intercept this guy and return true so that the other gestures are handled.
         * https://code.google.com/p/android/issues/detail?id=8233
         */
        public boolean onDown(MotionEvent e) {
            Log.d("GestureDetector --> onDown");
            return true;
        }
    });

    mGestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {

            if (mGestureDetector.onTouchEvent(event)) {
                return true;
            }

            if(event.getAction() == MotionEvent.ACTION_UP) {
                if(mIsScrolling ) {
                    Log.d("OnTouchListener --> onTouch ACTION_UP");
                    mIsScrolling  = false;
                    handleScrollFinished();
                };
            }

            return false;
        }
    };

    // attach the OnTouchListener to the image view
    mImageView.setOnTouchListener(mGestureListener);
}

4voto

Vous devriez jeter un coup d'œil à developer.Android.com/reference/Android/widget/Scroller.html. Cela pourrait notamment vous aider (trié par pertinence) :

isFinished();
computeScrollOffset();
getFinalY(); getFinalX(); and getCurrY() getCurrX()
getDuration()

Cela implique que vous devez créer un Scroller.

Si vous souhaitez utiliser le toucher, vous pouvez également utiliser GestureDetector et définir votre propre défilement de toile. L'exemple suivant crée un ScrollableImageView et pour l'utiliser, vous devez définir les dimensions de votre image. Vous pouvez définir votre propre plage de défilement et, une fois le défilement terminé, l'image est redessinée.

http://www.anddev.org/viewtopic.php?p=31487#31487

En fonction de votre code, vous devriez considérer invalidate(int l, int t, int r, int b) ; pour l'invalidation.

3voto

Aron Cederholm Points 2183
SimpleOnGestureListener.onFling() 

Il semble que cela se produise lorsqu'un défilement se termine (c'est-à-dire lorsque l'utilisateur lâche le doigt). C'est ce que j'utilise et cela fonctionne très bien pour moi.

2voto

prepbgg Points 1287

Pour y revenir après quelques mois, j'ai adopté une approche différente : j'utilise un gestionnaire (comme dans l'exemple Android Snake) pour envoyer un message à l'application toutes les 125 millisecondes, lui demandant de vérifier si un défilement a été lancé et si plus de 100 millisecondes se sont écoulées depuis le dernier événement de défilement.

Cela semble fonctionner assez bien, mais si quelqu'un voit des inconvénients ou des améliorations possibles, je lui en serai reconnaissant.

Le code correspondant se trouve dans la classe MyView :

public class MyView extends android.view.View {

...

private long timeCheckInterval = 125; // milliseconds
private long scrollEndInterval = 100;
public long latestScrollEventTime;
public boolean scrollInProgress = false;

public MyView(Context context) {
    super(context);
}

private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();

class timeCheckHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
        long now = System.currentTimeMillis();
        if (scrollInProgress && (now>latestScrollEventTime+scrollEndInterval)) {
                    scrollInProgress = false;

// Le défilement est terminé, il faut insérer le code ici.

// qui appelle la méthode doDrawing()

// pour redessiner le bitmap recentré à l'endroit où le défilement s'est terminé.

                    [ layout or view ].invalidate();
        }
        this.sleep(timeCheckInterval);
        }

        public void sleep(long delayMillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), delayMillis);
            }
    }
}

@Override protected void onDraw(Canvas canvas){
        super.onDraw(canvas);

// code pour dessiner un grand tampon bitmap sur le canevas de la vue // positionné de manière à tenir compte de tout défilement en cours.

}

public void doDrawing() {

// code pour faire un dessin détaillé (et qui prend du temps) // sur un grand tampon bitmap

// l'instruction suivante remet à zéro l'horloge Time Check // L'horloge est lancée pour la première fois lorsque // l'activité principale appelle cette méthode au démarrage de l'application.

        mTimeCheckHandler.sleep(timeCheckInterval);
}

// reste de la classe MyView

}

et dans la classe MyGestureDetector

public class MyGestureDetector extends SimpleOnGestureListener {

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
        float distanceY) {

    [MyView].scrollInProgress = true;
        long now = System.currentTimeMillis();  
    [MyView].latestScrollEventTime =now;

    [MyView].scrollX += (int) distanceX;
    [MyView].scrollY += (int) distanceY;

// l'instruction suivante appelle la méthode onDraw de la vue // qui trace le bitmap de la mémoire tampon sur l'écran // décalée pour tenir compte du scroll.

    [MyView].invalidate();

}

// reste de la classe MyGestureDetector

}

1voto

Mike Points 79

Je me suis penché sur cette même question. J'ai vu la réponse d'Akos Cz à votre question. J'ai créé quelque chose de similaire, mais avec ma version, j'ai remarqué que cela ne fonctionnait que pour un scroll normal - c'est-à-dire un scroll qui ne génère pas de fling. Mais si un fling est généré - indépendamment du fait que j'ai traité un fling ou non, alors il n'a PAS détecté l'"ACTION_UP" dans "onTouchEvent". Peut-être s'agit-il simplement d'un problème de mise en œuvre, mais si c'est le cas, je n'ai pas réussi à comprendre pourquoi.

Après une enquête plus poussée, j'ai remarqué que pendant un fling, l'"ACTION_UP" était passée dans "onFling" dans "e2" à chaque fois. J'ai donc pensé que cela devait être la raison pour laquelle il n'était pas traité dans "onTouchEvent" dans ces cas-là.

Pour que cela fonctionne, il m'a suffi d'appeler une méthode pour gérer l'"ACTION_UP" dans "onFling" et cela a fonctionné pour les deux types de défilement. Vous trouverez ci-dessous les étapes exactes de la mise en œuvre dans mon application :

-initialiser un booléen "gestureScrolling" à "false" dans un constructeur.

-Je l'ai mis à "true" dans "onScroll".

-J'ai créé une méthode pour gérer l'événement "ACTION_UP". Dans cet événement, j'ai réinitialisé "gestureSCrolling" à false, puis j'ai effectué le reste du traitement dont j'avais besoin.

-dans "onTouchEvent", si une "ACTION_UP" a été détectée et que "gestureScrolling" = true, alors j'ai appelé ma méthode pour gérer "ACTION_UP".

-Et la partie que j'ai fait qui était différente était : J'ai aussi appelé ma méthode pour gérer "ACTION_UP" à l'intérieur de "onFling".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X