Kann ich onScrollListener für eine ScrollView haben?

140

Ich verwende ein HorizontalScrollViewin einem Layout und muss identifizieren, dass der Benutzer den Start- und Endpunkt der Schriftrolle erreicht hat.

Denn ListViewich habe es mit einem versucht onScrollListenerund es ist möglich, den Start- und Endpunkt der Schriftrolle zu finden.

Ich habe versucht, dasselbe in meinem zu tun, Scrollviewaber es scheint nicht möglich zu sein. Gibt es andere Möglichkeiten, um das zu erreichen, was ich brauche?

arnp
quelle
3
Es ist möglich. Siehe die Antwort von user2695685. Kurz gesagt, das Folgende in reicht aus onStart: hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});in onStart () wo hsvist ein HorizontalScrollViewWerk.
Johnny Lambada
Akzeptiere jede nützliche Antwort. Wenn du deine eigene Antwort postest.
Ranjith Kumar
12
Warum ist es in Android so schwierig, ein Bildlaufereignis mit einer ScrollView zu erkennen? Das ist verrückt imo.
arbeitete

Antworten:

389

Jede Instanz von View ruft auf getViewTreeObserver(). Wenn ViewTreeObserverSie jetzt eine Instanz von halten , können Sie OnScrollChangedListener()diese mithilfe der Methode hinzufügen addOnScrollChangedListener().

Weitere Informationen zu dieser Klasse finden Sie hier .

Sie können jedes Bildlaufereignis erkennen - jedoch ohne die Koordinaten. Sie können sie jedoch mithilfe getScrollY()oder getScrollX()innerhalb des Listeners abrufen.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        int scrollY = rootScrollView.getScrollY(); // For ScrollView
        int scrollX = rootScrollView.getScrollX(); // For HorizontalScrollView
        // DO SOMETHING WITH THE SCROLL COORDINATES
    }
});
Zbun
quelle
6
Diese Antwort sollte als richtig markiert sein. hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});in onStart () wo hsvist ein HorizontalScrollViewfunktioniert. Ich vermute, dass dies auch für eine ScrollView funktioniert.
Johnny Lambada
3
genau. Das ist die beste Antwort. HorizontalScrollView muss nicht erweitert werden. Gerade mit einem ScrollView getestet, funktioniert hervorragend: final ScrollView sv = (ScrollView) findViewById (R.id.scrollViewArticle); sv.getViewTreeObserver (). addOnScrollChangedListener (neuer ViewTreeObserver.OnScrollChangedListener () {@Override public void onScrollChanged () {Log.d ("onScrollChanged Y", String.valueOf (sv.getScrollY));
Raphael C
5
Sie sollten zuerst ViewTreeObserver.isAlive ()
M.Salomaa
6
Der Beispielcode in der Antwort führt zu einem Speicherverlust. Da die Methode ist addund nicht set, bleiben alle Listener erhalten, bis sie explizit entfernt werden. Die anonyme Klasse, die im Beispiel als Listener-Implementierung verwendet wird, leckt also (zusammen mit allem, worauf sie verweist, dh der äußeren Klasse).
Brett Duncavage
7
Wahrscheinlich eine dumme Frage, aber was ist rootScrollView?
Iqbal
49

Dies könnte sehr nützlich sein. Verwenden Sie NestedScrollViewanstelle von ScrollView. Support Library 23.1 führte ein OnScrollChangeListenerzu NestedScrollView. Sie können also so etwas tun.

 myScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            Log.d("ScrollView","scrollX_"+scrollX+"_scrollY_"+scrollY+"_oldScrollX_"+oldScrollX+"_oldScrollY_"+oldScrollY);
            //Do something
        }
    });
Midhun Vijayakumar
quelle
2
Auf jeden Fall einfacher, als zu verfolgen, ob Sie den Listener zuvor mit getViewTreeObserver () hinzugefügt haben. Vielen Dank!
Anthony Mills
Aber wie kann man NestedScrollView als horizontale Bildlaufansicht verwenden? Cannt finden Sie eine Ressource
GensaGames
Was ist, wenn ich einen horizontalen Bildlauf verwenden möchte?
Nikita Axyonov
7
@ dazed'n'confused NestedScrollViewist Teil der Support-Bibliothek. Für die setOnScrollChangeListenerMethode sind keine Mindestversionsanforderungen erforderlich.
Tom
4
Nicht zu verwechseln mit View's, für setOnScrollChangeListenerdie API Level 23 erforderlich ist
Anders Ullnæss
18

Hier ist eine abgeleitete HorizontalScrollView, die ich geschrieben habe, um Benachrichtigungen über das Scrollen und das Scrollende zu verarbeiten. Es wird ordnungsgemäß behandelt, wenn ein Benutzer aufgehört hat, aktiv zu scrollen, und wenn es vollständig abgebremst ist, nachdem ein Benutzer losgelassen hat:

public class ObservableHorizontalScrollView extends HorizontalScrollView {
    public interface OnScrollListener {
        public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
        public void onEndScroll(ObservableHorizontalScrollView scrollView);
    }

    private boolean mIsScrolling;
    private boolean mIsTouching;
    private Runnable mScrollingRunnable;
    private OnScrollListener mOnScrollListener;

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        if (action == MotionEvent.ACTION_MOVE) {
            mIsTouching = true;
            mIsScrolling = true;
        } else if (action == MotionEvent.ACTION_UP) {
            if (mIsTouching && !mIsScrolling) {
                if (mOnScrollListener != null) {
                    mOnScrollListener.onEndScroll(this);
                }
            }

            mIsTouching = false;
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);

        if (Math.abs(oldX - x) > 0) {
            if (mScrollingRunnable != null) {
                removeCallbacks(mScrollingRunnable);
            }

            mScrollingRunnable = new Runnable() {
                public void run() {
                    if (mIsScrolling && !mIsTouching) {
                        if (mOnScrollListener != null) {
                            mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this);
                        }
                    }

                    mIsScrolling = false;
                    mScrollingRunnable = null;
                }
            };

            postDelayed(mScrollingRunnable, 200);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY);
        }
    }

    public OnScrollListener getOnScrollListener() {
        return mOnScrollListener;
    }

    public void setOnScrollListener(OnScrollListener mOnEndScrollListener) {
        this.mOnScrollListener = mOnEndScrollListener;
    }

}
ZaBlanc
quelle
1
Dies scheint besser zu sein als die ViewTreeObserver-Methode. Nur weil Sie in einer Aktivität mehrere Fragmente (z. B. 3 Fragmente mit verschiebbaren Registerkarten) mit ListViews und ScrollViews geladen haben und Ereignisse für eine bestimmte Ansicht registrieren müssen. Wenn der ViewTreeObserver registriert ist, werden Ereignisse ausgelöst, auch wenn die Ansicht nicht aktiv ist.
Torsten Ojaperv
3
@ZaBlanc - Können Sie mir bitte mitteilen, wie diese Klasse und die Listener aus meiner Aktivität, die die ScrollView enthält, initialisiert werden sollen? Ich habe es gut implementiert, aber die Listener geben noch keine Werte zurück.
Edmond Tamas
Das funktioniert tatsächlich! und keine Notwendigkeit für SDK> 23 dafür :)
Matan Dahan
5

Wenn Sie die Bildlaufposition einer Ansicht kennen möchten, können Sie die folgende Erweiterungsfunktion für die Ansichtsklasse verwenden:

fun View?.onScroll(callback: (x: Int, y: Int) -> Unit) {
    var oldX = 0
    var oldY = 0
    this?.viewTreeObserver?.addOnScrollChangedListener {
        if (oldX != scrollX || oldY != scrollY) {
            callback(scrollX, scrollY)
            oldX = scrollX
            oldY = scrollY
        }
    }
}
mcspayne
quelle
5

Sie können NestedScrollViewanstelle von verwenden ScrollView. Wenn Sie jedoch ein Kotlin Lambda verwenden, wissen Sie nicht, dass Sie NestedScrollViews setOnScrollChangeListeneranstelle von View (View API Level 23) möchten . Sie können dies beheben, indem Sie den ersten Parameter als NestedScrollView angeben.

nestedScrollView.setOnScrollChangeListener { _: NestedScrollView, scrollX: Int, scrollY: Int, _: Int, _: Int ->
    Log.d("ScrollView", "Scrolled to $scrollX, $scrollY")
}
Cristan
quelle
Dies sollte die akzeptierte Antwort sein, da die Methode, die einen viewTreeObserver verwendet, Speicherlecks verursacht und manuell entfernt werden muss, da sonst mehrere Bildlaufbeobachter hinzugefügt werden.
Ray Li
1
    // --------Start Scroll Bar Slide--------
    final HorizontalScrollView xHorizontalScrollViewHeader = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewHeader);
    final HorizontalScrollView xHorizontalScrollViewData = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewData);
    xHorizontalScrollViewData.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int scrollX; int scrollY;
            scrollX=xHorizontalScrollViewData.getScrollX();
            scrollY=xHorizontalScrollViewData.getScrollY();
            xHorizontalScrollViewHeader.scrollTo(scrollX, scrollY);
        }
    });
    // ---------End Scroll Bar Slide---------
Wirote W.
quelle
Bitte versuchen Sie, eine Beschreibung hinzuzufügen, um Ihren Code zu unterstützen, damit jeder den Teig versteht
Aniruddha Das
Dieser Code ist einer anderen Antwort sehr ähnlich , nicht wahr?
Sergii
1

Sie können eine benutzerdefinierte ScrollView-Klasse definieren und eine Schnittstelle hinzufügen, die beim Scrollen wie folgt aufgerufen wird:

public class ScrollChangeListenerScrollView extends HorizontalScrollView {


private MyScrollListener mMyScrollListener;

public ScrollChangeListenerScrollView(Context context) {
    super(context);
}

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

public ScrollChangeListenerScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}


public void setOnMyScrollListener(MyScrollListener myScrollListener){
    this.mMyScrollListener = myScrollListener;
}


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

}

public interface MyScrollListener {
    void onScrollChange(View view,int scrollX,int scrollY,int oldScrollX, int oldScrollY);
}

}
Chuanxing Zhao
quelle