Détecter la fin de ScrollView

J’ai une application qui a une activité qui utilise un ScrollView . Je dois détecter quand l’utilisateur arrive au bas de ScrollView . J’ai fait une recherche sur Google et j’ai trouvé cette page où est expliqué. Mais, dans l’exemple, les gars étendent ScrollView . Comme je l’ai dit, je dois prolonger l’activité.

Donc, j’ai dit “ok, essayons de créer une classe personnalisée étendant ScrollView , redéfinissons 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 une java.lang.ClassCastException . J’ai changé les balises dans mon XML mais, évidemment, cela ne fonctionne pas. Mes questions sont: pourquoi, si ScrollViewExt étend ScrollView , jette à mon visage une ClassCastException ? existe-t-il un moyen de détecter la fin du défilement sans trop déconner?

Merci à vous

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

    

Je l’ai changé de TextView à WebView pour pouvoir justifier le texte à l’intérieur. 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 complètement lus”. Ma classe étendue s’appelle ScrollViewEx t. Si je change la balise 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 y ait une solution …

Merci pour vos réponses!

L’a fait!

Outre le correctif qu’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, AtsortingbuteSet attrs, int defStyle) { super(context, attrs, defStyle); } public ScrollViewExt(Context context, AtsortingbuteSet 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 dit Alexandre, mettez le nom du paquet dans la balise XML (my fault), faites en sorte que ma classe Activity implémente l’interface créée auparavant, puis rassemblez tous les éléments:

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

Et dans la méthode OnScrollChanged, depuis 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!

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 } } }); 
  • Il n'est pas nécessaire 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 de l'enfant direct de la vue de défilement est match_parent ou wrap_content .

La réponse de Fustigador était géniale, mais j’ai trouvé que certains appareils (comme Samsung Galaxy Note V) ne pouvaient pas atteindre 0, ont 2 points à gauche, après le calcul. Je suggère d’append 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 } } 

Toutes ces réponses sont si 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 un ScrollView horizontal, vous pouvez obtenir la même chose avec canScrollHorizontally(int)

MODIFIER

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

    

EDIT END

Pourriez-vous poster le contenu XML?

Je pense que vous pouvez simplement append 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 } } }); 

Vous pouvez utiliser NestedScrollView de la bibliothèque de NestedScrollView et son interface NestedScrollView.OnScrollChangeListener .

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

Si votre application cible l’API 23 ou une version ScrollView , vous pouvez utiliser la méthode suivante sur ScrollView :

 View.setOnScrollChangeListener(OnScrollChangeListener listener) 

Suivez ensuite l’exemple que @Fustigador a décrit dans sa réponse. Notez cependant que @Will décrit, vous devriez envisager d’append un petit tampon au cas où l’utilisateur ou le système ne serait pas en mesure d’atteindre le bas de la liste pour une raison quelconque.

Il convient également de noter que l’écouteur de changement de défilement est parfois appelé avec des valeurs négatives ou des valeurs supérieures à la hauteur de la vue. Vraisemblablement, ces valeurs représentent le «momentum» de l’action de défilement. Cependant, à moins d’être manipulées de manière appropriée (étage / abs), elles peuvent entraîner des problèmes de détection de la direction du défilement lorsque la vue défile en haut ou en bas de la plage.

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

Nous devons toujours append scrollView.getPaddingBottom() pour correspondre à la hauteur de défilement complète, car une vue de défilement du temps contient un remplissage dans le fichier xml pour que ce cas 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 } } } }); 

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

 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; } } 

Ensuite, il suffit de mettre l'auditeur

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

Vous pouvez faire la même chose si vous faites comme

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

mais vous devez fournir un indicateur de classe en ajoutant cet écouteur.

Pour déterminer si vous êtes à la fin de votre ScrollView personnalisé, vous pouvez également utiliser une variable membre et stocker la dernière position y. Vous pouvez ensuite 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; }