89 votes

Android : Détecter quand ScrollView arrête de défiler

J'utilise un ScrollView dans Android et où la partie visible de la ScrollView est de la même taille qu'une des cellules à l'intérieur du Scrollview . Chaque "cellule" a la même hauteur. Donc, ce que j'essaie de faire, c'est de me mettre en position après que l'élément ScrollView a été déroulé.

Actuellement, je détecte le moment où l'utilisateur a touché l'écran. ScrollView et quand ils ont commencé à défiler et à travailler à partir de là, mais c'est assez bogué. Il faut aussi qu'il fonctionne lorsque l'utilisateur appuie sur le bouton et que le défilement se fait puis ralentit.

Sur l'iPhone il y a une fonction qui est quelque chose comme didDecelerate et là je peux faire tout le code que je veux quand le ScrollView a fini de défiler. Est-ce que cela existe sous Android ? Ou existe-t-il un code que je pourrais consulter pour trouver une meilleure façon de procéder ?

J'ai parcouru la documentation d'Android et je n'ai rien trouvé de tel.

106voto

tulio84z Points 707

J'ai récemment dû mettre en œuvre la fonction que vous avez décrite. Ce que j'ai fait, c'est qu'un Runnable vérifie si le ScrollView a arrêté de défiler en comparant la valeur renvoyée par getScrollY() lorsque le onTouchEvent est déclenché pour la première fois avec la valeur renvoyée après un temps défini par la variable nouveauCheck .

Voir le code ci-dessous (solution fonctionnelle) :

public class MyScrollView extends ScrollView{

private Runnable scrollerTask;
private int initialPosition;

private int newCheck = 100;
private static final String TAG = "MyScrollView";

public interface OnScrollStoppedListener{
    void onScrollStopped();
}

private OnScrollStoppedListener onScrollStoppedListener;

public MyScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);

    scrollerTask = new Runnable() {

        public void run() {

            int newPosition = getScrollY();
            if(initialPosition - newPosition == 0){//has stopped

                if(onScrollStoppedListener!=null){

                    onScrollStoppedListener.onScrollStopped();
                }
            }else{
                initialPosition = getScrollY();
                MyScrollView.this.postDelayed(scrollerTask, newCheck);
            }
        }
    };
}

public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){
    onScrollStoppedListener = listener;
}

public void startScrollerTask(){

    initialPosition = getScrollY();
    MyScrollView.this.postDelayed(scrollerTask, newCheck);
}

}

Alors je l'ai fait :

scroll.setOnTouchListener(new OnTouchListener() {

        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_UP) {

                scroll.startScrollerTask();
            }

            return false;
        }
});
scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() {

        public void onScrollStopped() {

            Log.i(TAG, "stopped");

        }
});

J'ai utilisé quelques idées provenant d'autres réponses pour faire cela dans mon application. J'espère que cela vous aidera. Si vous avez des questions, n'hésitez pas à les poser. Merci.

12 votes

Merci, cela fait des années que je cherche quelque chose comme ça. Je l'ai utilisé pour le HorizontalScrollView à la place, qui (évidemment) fonctionne aussi bien. Seul getScrollY() doit être remplacé par getScrollX().

2 votes

Je les ai tous essayés ici. C'est le SEUL qui fonctionne.

0 votes

Même en cliquant sur le scrollview, le message "stopped" apparaît. Cela ne devrait fonctionner que si l'utilisateur fait défiler le Scrollview. Alors que devons-nous faire ?

24voto

concept Points 332

Voici encore un autre correctif au bug de l'événement OnEndScroll dans le ScrollView.

Il s'inspire de hambonious réponse. Déposez simplement cette classe dans votre projet (changez le package pour qu'il corresponde au vôtre) et utilisez le xml suivant

package com.thecrag.components.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

public class ResponsiveScrollView extends ScrollView {

    public interface OnEndScrollListener {
        public void onEndScroll();
    }

    private boolean mIsFling;
    private OnEndScrollListener mOnEndScrollListener;

    public ResponsiveScrollView(Context context) {
        this(context, null, 0);
    }

    public ResponsiveScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY);
        mIsFling = true;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);
        if (mIsFling) {
            if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) {
                if (mOnEndScrollListener != null) {
                    mOnEndScrollListener.onEndScroll();
                }
                mIsFling = false;
            }
        }
    }

    public OnEndScrollListener getOnEndScrollListener() {
        return mOnEndScrollListener;
    }

    public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) {
        this.mOnEndScrollListener = mOnEndScrollListener;
    }

}

en changeant à nouveau le nom du paquet pour qu'il corresponde à votre projet

<com.thecrag.components.ui.ResponsiveScrollView
        android:id="@+id/welcome_scroller"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/welcome_scroll_command_help_container"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/welcome_header_text_thecrag"
        android:layout_margin="6dp">
    ....
</com.thecrag.components.ui.ResponsiveScrollView>

2 votes

+1 Belle réponse simple. Je change la valeur de la différence 2 a 0.5f pour refléter l'original ScrollView 's MAX_SCROLL_FACTOR valeur.

2 votes

Cette implémentation est intéressante mais elle échoue lorsque la vue défilante n'est pas remplie au départ. Voici une solution de contournement (pour une vue de défilement horizontale, donc remplacez tous les x par y pour obtenir une vue verticale) : @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY) ; if (Math.abs(x - oldX) < SCROLL_FINISHED_THRESHOLD && x ! = lastStopX || x >= getMeasuredWidth() || x == 0) { lastStopX = x ; if (mOnEndScrollListener != null) { mOnEndScrollListener.onScrollEnded() ; } }. } }

0 votes

Cela ne fonctionne pas toujours. Comme dans l'émulateur, le public void fling(int velocityY) n'est pas toujours appelé. Le seuil de fin de défilement est parfois supérieur à 20 px.

9voto

Ross Hambrick Points 1869

J'ai sous-classé (Horizontal)ScrollView et fait quelque chose comme ceci :

@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
    if (Math.abs(x - oldX) > SlowDownThreshold) {  
        currentlyScrolling = true;
    } else {
        currentlyScrolling = false;
        if (!currentlyTouching) {
            //scrolling stopped...handle here
        }
    }
    super.onScrollChanged(x, y, oldX, oldY);
}

J'ai utilisé une valeur de 1 pour le SlowDownThreshold car il semble toujours être la différence du dernier événement onScrollChanged.

Pour que cela se comporte correctement lorsque l'on glisse lentement, j'ai dû faire ceci :

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            currentlyTouching = true;
    }
    return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouch(View view, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            currentlyTouching = false;
            if (!currentlyScrolling) {
                //I handle the release from a drag here
                return true;
            }
    }
    return false;
}

0 votes

Belle réponse. Un seul ajout. Dans onInterceptTouchEvent() vous devez aussi gérer MotionEvent.ACTION_UP et MotionEvent.ACTION_CANCEL sinon currentlyTouching restera vrai dans le cas où vous ouvrez une autre activité sur le clic.

0 votes

Une autre correction : dans onScrollChanged() vous devez aussi vérifier la fin du scroll même si le ralentissement n'est pas encore 1. Comme ceci : boolean end = (oldx > x) ? (x <= 0) : (x >= getMaxScrollAmount()) ; if (Math.abs(x - oldx) > 1 && !end) { scroll start ; } else { scroll end }

0 votes

Cela ne fonctionne pas sur certains appareils, par exemple le GS3. Sur mon GS3, la différence entre y et l'ancien y PEUT être de 1 au milieu du défilement. En revanche, cela fonctionne parfaitement sur mon Nexus 4. Je ne sais pas pourquoi.

5voto

ZeroG Points 41

Mon approche pour cette question est d'utiliser une minuterie pour vérifier les deux "événements" suivants.

1) onScrollChanged() a cessé d'être appelé

2) Le doigt de l'utilisateur est retiré de la fenêtre de défilement.

public class CustomScrollView extends HorizontalScrollView {

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

Timer ntimer = new Timer();
MotionEvent event;

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) 
{
    checkAgain();
    super.onScrollChanged(l, t, oldl, oldt);
}

public void checkAgain(){
    try{
        ntimer.cancel();
        ntimer.purge();
    }
    catch(Exception e){}
    ntimer = new Timer();
    ntimer.schedule(new TimerTask() {

        @Override
        public void run() {

            if(event.getAction() == MotionEvent.ACTION_UP){
                // ScrollView Stopped Scrolling and Finger is not on the ScrollView
            }
            else{
                // ScrollView Stopped Scrolling But Finger is still on the ScrollView
                checkAgain();
            }
        }
    },100);

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    this.event = event; 
    return super.onTouchEvent(event);
    }
}

2voto

Felix Points 33944

Je pense que cette question a déjà été soulevée dans le passé. A ma connaissance, vous ne pouvez pas facilement détecter cela. Je vous suggère de jeter un coup d'oeil à ScrollView.java (c'est comme ça qu'on fait les choses au pays des Androïdes. :) ) et déterminez comment vous pouvez étendre la classe pour fournir la fonctionnalité que vous recherchez. C'est ce que j'essaierais de faire en premier :

 @Override
 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
     if (mScroller.isFinished()) {
         // do something, for example call a listener
     }
 }

0 votes

Joli, je n'avais pas remarqué la méthode isFinished !

3 votes

Merci. Comment puis-je accéder à la méthode isFinished() ? J'ai jeté un coup d'œil au fichier ScrollView.java et mScroller est privé et je ne vois aucun moyen d'y accéder.

0 votes

Hmm, en effet. Je n'ai pas regardé d'assez près. C'est pourquoi j'ai dit que j'essaierais ça. premièrement :). Vous pourriez faire d'autres choses, comme suivre les appels à onScrollChanged , scrollTo ou toute autre méthode liée au défilement. Vous pouvez également copier l'ensemble de la page ScrollView et faire en sorte que ce champ protected . Je ne le recommanderais pas, cependant.

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