web-dev-qa-db-fra.com

Détecter la fin de ScrollView

J'ai une application qui a une activité qui utilise un ScrollView. J'ai besoin de détecter quand l'utilisateur arrive au bas de la ScrollView. J'ai fait quelques recherches sur Google et j'ai trouvé cette page où est expliqué. Mais, dans l'exemple, ce gars étend ScrollView. Comme je l'ai dit, j'ai besoin d'étendre l'activité.

Alors, j'ai dit "ok, essayons de créer une classe personnalisée étendant ScrollView, substituons la méthode onScrollChanged(), détectons la fin du défilement et agissons en conséquence".

Je l'ai fait, mais dans cette ligne:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);

il jette un Java.lang.ClassCastException. J'ai changé le <ScrollView> balises dans mon XML mais, évidemment, cela ne fonctionne pas. Mes questions sont les suivantes: pourquoi, si ScrollViewExt étend ScrollView, jette sur mon visage un ClassCastException? y at-il un moyen de détecter la fin du défilement sans trop déranger?

Merci les gens.

EDIT: Comme promis, voici le morceau de mon XML qui compte:

<ScrollView
        Android:id="@+id/scrollView1"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" >


        <WebView
            Android:id="@+id/textterms"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:gravity="center_horizontal"
            Android:textColor="@Android:color/black" />

    </ScrollView>

Je l'ai changé de TextView en WebView pour pouvoir justifier le texte qu'il contient. Ce que je veux réaliser, c’est que le bouton "Accepter" ne s’active pas tant que les termes du contrat ne sont pas entièrement lus ". Ma classe étendue s'appelle ScrollViewExt. Si je change le tag ScrollView pour ScrollViewExt il jette un

Android.view.InflateException: Binary XML file line #44: Error inflating class ScrollViewExt

car il ne comprend pas la balise ScrollViewEx. Je ne pense pas qu'il a une solution ...

Merci pour vos réponses!

63
Fustigador

L'a fait!

En plus du correctif que Alexandre m'a gentiment fourni, je devais créer une interface:

public interface ScrollViewListener {
    void onScrollChanged(ScrollViewExt scrollView, 
                         int x, int y, int oldx, int oldy);
}

Ensuite, j'ai dû remplacer la méthode OnScrollChanged de ScrollView dans mon ScrollViewExt:

public class ScrollViewExt extends ScrollView {
    private ScrollViewListener scrollViewListener = null;
    public ScrollViewExt(Context context) {
        super(context);
    }

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

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

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, l, t, oldl, oldt);
        }
    }
}

Maintenant, comme Alexandre l'a dit, mettez le nom du paquet dans la balise XML (de ma faute), faites que ma classe d'activité implémente l'interface créée précédemment, puis, rassemblez le tout:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);
scroll.setScrollViewListener(this);

Et dans la méthode OnScrollChanged, à partir de l'interface ...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff == 0) {
        // do stuff
    }
}

Et ça a marché!

Merci beaucoup pour votre aide, Alexandre!

104
Fustigador

J'ai trouvé un moyen simple de détecter ceci:

   scrollView.getViewTreeObserver()
       .addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (scrollView.getChildAt(0).getBottom()
                     <= (scrollView.getHeight() + scrollView.getScrollY())) {
                    //scroll view is at bottom
                } else {
                    //scroll view is not at bottom
                }
            }
        });
  • N'a pas besoin de personnaliser ScrollView.
  • Scrollview ne peut héberger qu'un seul enfant direct, donc scrollView.getChildAt (0) est correct.
  • Cette solution est correcte même si la hauteur d'enfant direct de la vue de défilement est match_parent ou wrap_content.
56

Toutes ces réponses sont tellement compliquées, mais il existe une méthode intégrée simple qui accomplit ceci: canScrollVertically(int)

Par exemple:

@Override
public void onScrollChanged() {
    if (!scrollView.canScrollVertically(1)) {
        // bottom of scroll view
    }
    if (!scrollView.canScrollVertically(-1)) {
        // top of scroll view
    }
}

Cela fonctionne également avec RecyclerView, ListView et en fait toute autre vue puisque la méthode est implémentée sur View.

Si vous avez une ScrollView horizontale, vous pouvez obtenir la même chose avec canScrollHorizontally(int)

30
yuval

La réponse de Fustigador était excellente, mais j'ai trouvé un appareil (comme le Samsung Galaxy Note V) ne pouvant pas atteindre 0, il reste 2 points, après le calcul. Je suggère d'ajouter un petit tampon comme ci-dessous:

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff <= 10) {
        // do stuff
    }
}
16
Will

EDIT

Avec le contenu de votre XML, je peux voir que vous utilisez un ScrollView. Si vous souhaitez utiliser votre affichage personnalisé, vous devez écrire com.votre.packagename.ScrollViewExt et vous pourrez l’utiliser dans votre code.

<com.your.packagename.ScrollViewExt
    Android:id="@+id/scrollView1"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" >

    <WebView
        Android:id="@+id/textterms"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:gravity="center_horizontal"
        Android:textColor="@Android:color/black" />

</com.your.packagename.ScrollViewExt>

EDIT END

Pourriez-vous poster le contenu XML?

Je pense que vous pourriez simplement ajouter un écouteur de défilement et vérifier si le dernier élément affiché est le dernier de la liste comme:

mListView.setOnScrollListener(new OnScrollListener() {      
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        if(view.getLastVisiblePosition()==(totalItemCount-1)){
            //dosomething
        }
    }
});
10
Alexandre B.

Vous pouvez utiliser la bibliothèque de support NestedScrollView et c'est NestedScrollView.OnScrollChangeListener interface.

https://developer.Android.com/reference/Android/support/v4/widget/NestedScrollView.html

Si votre application cible l'API 23 ou une version ultérieure, vous pouvez également utiliser la méthode suivante sur le ScrollView:

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

Suivez ensuite l’exemple décrit par @Fustigador dans sa réponse. Notez cependant que, comme @Will est décrit, vous devez envisager l’ajout d’un petit tampon au cas où l’utilisateur ou le système ne pourrait pas atteindre le bas de la liste pour une raison quelconque.

Il convient également de noter que l’auditeur de changement de défilement sera parfois appelé avec des valeurs négatives ou des valeurs supérieures à la hauteur de vue. Vraisemblablement, ces valeurs représentent le "momentum" de l'action de défilement. Cependant, s'ils ne sont pas gérés correctement (sol/abs), ils peuvent poser des problèmes pour détecter la direction de défilement lorsque la vue défile vers le haut ou le bas de la plage.

https://developer.Android.com/reference/Android/view/View.html#setOnScrollChangeListener (Android.view.View.OnScrollChangeListener)

4
TheIT

Nous devrions toujours ajouter scrollView.getPaddingBottom() pour correspondre à la hauteur totale de la vue de défilement, car une vue défilée temporelle a un remplissage dans le fichier xml afin que cela ne fonctionne pas.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (scrollView != null) {
               View view = scrollView.getChildAt(scrollView.getChildCount()-1);
               int diff = (view.getBottom()+scrollView.getPaddingBottom()-(scrollView.getHeight()+scrollView.getScrollY()));

          // if diff is zero, then the bottom has been reached
               if (diff == 0) {
               // do stuff
                }
            }
        }
    });
3
Pranav
scrollView = (ScrollView) findViewById(R.id.scrollView);

    scrollView.getViewTreeObserver()
            .addOnScrollChangedListener(new 
            ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {

                    if (!scrollView.canScrollVertically(1)) {
                        // bottom of scroll view
                    }
                    if (!scrollView.canScrollVertically(-1)) {
                        // top of scroll view


                    }
                }
            });
3
Sweta Chandarana

La plupart des réponses fonctionnent à côté d'un fait que lorsque vous faites défiler l'écran vers le bas, l'auditeur est déclenché plusieurs fois, ce qui dans mon cas est indésirable. Pour éviter ce comportement, j'ai ajouté un indicateur scrollPositionChanged qui vérifie si la position du défilement a même changé avant d'appeler à nouveau la méthode.

public class EndDetectingScrollView extends ScrollView {
    private boolean scrollPositionChanged = true;

    private ScrollEndingListener scrollEndingListener;

    public interface ScrollEndingListener {
        void onScrolledToEnd();
    }

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

        View view = this.getChildAt(this.getChildCount() - 1);
        int diff = (view.getBottom() - (this.getHeight() + this.getScrollY()));
        if (diff <= 0) {
            if (scrollPositionChanged) {
                scrollPositionChanged = false;
                if (scrollEndingListener != null) {
                    scrollEndingListener.onScrolledToEnd();
                }
            }
        } else {
            scrollPositionChanged = true;
        }
    }

    public void setScrollEndingListener(ScrollEndingListener scrollEndingListener) {
        this.scrollEndingListener = scrollEndingListener;
    }
}

Puis il suffit de mettre auditeur

scrollView.setScrollEndingListener(new EndDetectingScrollView.ScrollEndingListener() {
    @Override
    public void onScrolledToEnd() {
        //do your stuff here    
    }
});

Vous pouvez faire la même chose si vous aimez

scrollView.getViewTreeObserver().addOnScrollChangedListener(...)

mais vous devez fournir un indicateur de classe à votre ajout de cet écouteur.

2
Phatee P

Pour déterminer si vous êtes à la fin de votre programme personnalisé ScrollView, vous pouvez également utiliser une variable membre et stocker la dernière position y. Ensuite, vous pouvez comparer la dernière position y avec la position de défilement actuelle.

private int scrollViewPos;

...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {

   //reached end of scrollview
   if (y > 0 && scrollViewPos == y){
      //do something
   }

   scrollViewPos = y;
}
1
Willi

Je voulais montrer/cacher un FAB avec un décalage avant le bas de la vue à défilement. Voici la solution que j'ai trouvée (Kotlin):

scrollview.viewTreeObserver.addOnScrollChangedListener {
    if (scrollview.scrollY < scrollview.computeVerticalScrollRange() - scrollview.height - offset) {
        // fab.hide()
    } else {
        // fab.show()
    }
}
0
G00fY