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 ScrollViewEx
t. 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!
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!
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
}
}
});
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)
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
}
}
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
}
}
});
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.
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
}
}
}
});
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
}
}
});
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.
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;
}
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()
}
}