Erkennen Sie das Ende von ScrollView

76

Ich habe eine App, die eine Aktivität hat, die a verwendet ScrollView. Ich muss erkennen, wann der Benutzer dem auf den Grund geht ScrollView. Ich habe ein bisschen gegoogelt und diese Seite gefunden, auf der es erklärt wird. Aber im Beispiel erweitert sich dieser Typ ScrollView. Wie gesagt, ich muss die Aktivität erweitern.

Also sagte ich: "Okay, versuchen wir, eine benutzerdefinierte Klasse zu erweitern ScrollView, die onScrollChanged()Methode zu überschreiben , das Ende des Bildlaufs zu erkennen und entsprechend zu handeln."

Ich habe es getan, aber in dieser Zeile:

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

es wirft ein java.lang.ClassCastException. Ich habe die <ScrollView>Tags in meinem XML geändert , aber es funktioniert offensichtlich nicht. Meine Fragen sind: Warum, wenn es sich ScrollViewExtausdehnt ScrollView, wirft es mir ein Gesicht ins Gesicht ClassCastException? Gibt es eine Möglichkeit, das Ende des Bildlaufs zu erkennen, ohne zu viel durcheinander zu bringen?

Danke Leute.

EDIT: Wie versprochen, hier ist der Teil meines XML, der zählt:

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

Habe ich es von TextViewzu der WebViewLage sein , den Text innerhalb rechtfertigen. Was ich erreichen möchte, ist die Sache "Akzeptieren" wird erst aktiviert, wenn die Vertragsbedingungen vollständig gelesen sind. Meine erweiterte Klasse heißt ScrollViewExt. Wenn ich das Tag ScrollViewdafür ändere ScrollViewExt, wirft es ein

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

weil es das Tag nicht versteht ScrollViewEx. Ich glaube nicht, dass es eine Lösung gibt ...

Danke für deine Antworten!

Fustigador
quelle
Keine Notwendigkeit für benutzerdefinierte Klassen oder komplexe Erweiterungen. Verwenden canScrollVertically(int)
Sie

Antworten:

108

Erledigt!

Abgesehen von dem Fix, den Alexandre mir freundlicherweise zur Verfügung stellte, musste ich ein Interface erstellen:

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

Dann musste ich die OnScrollChanged-Methode von ScrollView in meinem ScrollViewExt überschreiben:

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

Fügen Sie nun, wie Alexandre sagte, den Paketnamen in das XML-Tag ein (mein Fehler), lassen Sie meine Activity-Klasse die zuvor erstellte Schnittstelle implementieren und fügen Sie dann alles zusammen:

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

Und in der Methode OnScrollChanged von der Schnittstelle ...

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

Und es hat funktioniert!

Vielen Dank für Ihre Hilfe, Alexandre!

Fustigador
quelle
Ich versuche, diese Antwort in meiner Anwendung zu verwenden, und das Problem ist, dass -scrollView.getHeight () immer gleich view.getBottom () ist, während scrollView.getScrollY () immer 0 ist. Haben Sie eine Idee, warum das so ist? ?
Marko Cakic
Vielen Dank, das ist großartig, da es ermöglicht, Pfeile oben und unten in der Bildlaufansicht zu erstellen und sie je nach Position der Bildlaufansicht ein- oder auszublenden, um den Benutzer darauf aufmerksam zu machen, dass weitere Elemente zu sehen sind ...
Lumis
@MarkoCakic Sehen Sie sich diese Antwort an, um die Höhe des Inhalts einer Bildlaufansicht zu ermitteln. stackoverflow.com/questions/3609297/…
Uselessinfo
Da scrollViewes nur ein Kind gibt view = scrollView.getChildAt(0)und scrollView.getScrollY()==y in Ihrer Funktion, könnte die Funktion oben damit geändert werden, anstatt auf redundante Daten zuzugreifen.
Laaptu
Ich habe eine Frage: Ich aktualisiere meine Bildlaufansicht dynamisch, damit ich dies nicht bei scrollChanged möchte. Ich möchte feststellen, ob der Benutzer nach oben gescrollt hat (Autoscroll ausschalten), wenn der Benutzer nach unten gescrollt hat (Autoscroll einschalten) Hilfe bitte.
AL̲̳I
81

Ich habe einen einfachen Weg gefunden, dies zu erkennen:

   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
                }
            }
        });
  • ScrollView muss nicht angepasst werden.
  • Scrollview kann nur ein direktes untergeordnetes Element hosten, daher ist scrollView.getChildAt (0) in Ordnung.
  • Diese Lösung ist richtig, auch wenn die Höhe des direkten untergeordneten Elements der Bildlaufansicht match_parent oder wrap_content ist .
Denken Sie zweimal an Code
quelle
Vielen Dank! Funktioniert!
DmitryKanunnikoff
Schöne Lösung! Vielen Dank.
Lee Hounshell
8
Um zu vermeiden, dass Bottom mehrmals angerufen wird, ersetzen Sie "<=" durch "=="
Harshil Pansare
Perfekt mit einigen kleinen Anpassungen. :)
John T
1
DANKE, wir sollten ScrollView nicht erweitern müssen, um etwas so Gemeinsames zu tun.
Andrew Koster
67

Alle diese Antworten sind so kompliziert, aber es gibt eine einfache integrierte Methode, die dies erreicht: canScrollVertically(int)

Zum Beispiel:

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

Dies funktioniert auch mit RecyclerView, ListView und jeder anderen Ansicht, da die Methode in implementiert ist View.

Wenn Sie eine horizontale ScrollView haben, kann das gleiche mit erreicht werden canScrollHorizontally(int)

Yuval
quelle
8
Perfekte Antwort! Geprüft!
LionisIAm
4
wirklich geholfen. Danke !!
Nik
4
Es muss eine AKZEPTIERTE ANTWORT sein, es ist einfach und funktioniert perfekt
3
Der Vollständigkeit halber ist !scrollView.canScrollHorizontally(1)zu prüfen, ob es sich bereits am rechten und !scrollView.canScrollHorizontally(-1)am linken Ende befindet.
Keithmaxx
1
perfekte Lösung!
Dimitri Schultheis
17

Die Antwort von Fustigador war großartig, aber ich fand, dass ein Gerät (wie das Samsung Galaxy Note V) nicht 0 erreichen kann und nach der Berechnung noch 2 Punkte übrig hat. Ich schlage vor, einen kleinen Puffer wie unten hinzuzufügen:

@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
    }
}
Wille
quelle
11
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


                    }
                }
            });
Shweta Chandarana
quelle
10

BEARBEITEN

Mit dem Inhalt Ihres XML kann ich sehen, dass Sie eine ScrollView verwenden. Wenn Sie Ihre benutzerdefinierte Ansicht verwenden möchten, müssen Sie com.your.packagename.ScrollViewExt schreiben, damit Sie sie in Ihrem Code verwenden können.

<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

Könnten Sie den XML-Inhalt posten?

Ich denke, Sie könnten einfach einen Scroll-Listener hinzufügen und prüfen, ob das zuletzt angezeigte Element das letzte in der Listenansicht ist, wie:

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
        }
    }
});
Alexandre B.
quelle
Ich werde es morgen tun, ich bin jetzt nicht bei der Arbeit. Aber ich sehe, Sie schlagen eine ListView vor. Ich verwende keine ListView, sondern nur eine TextView in einer ScrollView.
Fustigador
Oh, tut mir leid, ich habe an ListView gedacht. Könnten Sie das von setContentViewl aufgerufene xm mit Ihrer benutzerdefinierten Ansicht veröffentlichen?
Alexandre B.
4

Sie können die Support-Bibliothek NestedScrollViewund ihre NestedScrollView.OnScrollChangeListenerBenutzeroberfläche verwenden.

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

Wenn Ihre App auf API 23 oder höher abzielt, können Sie alternativ die folgende Methode verwenden ScrollView:

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

Folgen Sie dann dem Beispiel, das @Fustigador in seiner Antwort beschrieben hat. Beachten Sie jedoch, dass Sie, wie bei @Will beschrieben, in Betracht ziehen sollten, einen kleinen Puffer hinzuzufügen, falls der Benutzer oder das System aus irgendeinem Grund nicht in der Lage ist, das vollständige Ende der Liste zu erreichen.

Erwähnenswert ist auch, dass der Listener für Bildlaufänderungen manchmal mit negativen Werten oder Werten aufgerufen wird, die größer als die Ansichtshöhe sind. Vermutlich repräsentieren diese Werte den "Impuls" der Bildlaufaktion. Wenn sie jedoch nicht richtig gehandhabt werden (Boden / Bauch), können sie Probleme beim Erkennen der Bildlaufrichtung verursachen, wenn die Ansicht zum oberen oder unteren Rand des Bereichs gescrollt wird.

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

Das es
quelle
4

Wir sollten immer hinzufügen scrollView.getPaddingBottom(), um der vollen Höhe der Bildlaufansicht zu entsprechen, da die Bildlaufansicht manchmal eine Auffüllung in der XML-Datei aufweist, sodass dieser Fall nicht funktioniert.

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
                }
            }
        }
    });
Pranav
quelle
2

Um festzustellen, ob Sie sich am Ende Ihrer benutzerdefinierten ScrollViewPosition befinden, können Sie auch eine Elementvariable verwenden und die letzte y-Position speichern. Dann können Sie die letzte y-Position mit der aktuellen Bildlaufposition vergleichen.

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;
}
Willi
quelle
2

Die meisten Antworten funktionieren neben der Tatsache, dass der Listener mehrmals ausgelöst wird, wenn Sie nach unten scrollen , was in meinem Fall unerwünscht ist . Um dieses Verhalten zu vermeiden, habe ich das Flag scrollPositionChanged hinzugefügt , das prüft, ob sich die Bildlaufposition überhaupt geändert hat, bevor die Methode erneut aufgerufen wird.

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

Dann einfach den Listener einstellen

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

Sie können das Gleiche tun, wenn Sie es mögen

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

Sie müssen jedoch ein Flag aus der Klasse angeben, um diesen Listener hinzuzufügen.

Phatee P.
quelle
Nett! Funktioniert gut für Pre-API Level 23!
Abhiroop Nandi Ray
0

Ich wollte ein FAB mit einem Versatz ganz unten in der Bildlaufansicht ein- / ausblenden. Dies ist die Lösung, die ich mir ausgedacht habe (Kotlin):

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

Ich habe die Lösungen im Internet durchgesehen. In dem Projekt, an dem ich arbeite, haben die meisten Lösungen nicht funktioniert. Die folgenden Lösungen funktionieren gut für mich.

Verwenden von onScrollChangeListener (funktioniert mit API 23):

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

                int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollY;

                if(scrollY==0){
                    //top detected
                }
                if(bottom==0){
                    //bottom detected
                }
            }
        });
    }

Verwenden von scrollChangeListener auf TreeObserver

 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollView.getScrollY();

            if(scrollView.getScrollY()==0){
                //top detected
            }
            if(bottom==0) {
                //bottom detected
            }
        }
    });

Hoffe diese Lösung hilft :)

Arsam
quelle