25

I have a fixed content in my text view inside a scroll view. When the user scrolls to a certain position, I would like to start an activity or trigger a Toast.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/scroller"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:id="@+id/story" android:layout_height="wrap_content" android:text="@string/lorem" android:gravity="fill" /> </LinearLayout> </ScrollView> 

My problem is in implementing the protected method onScrollChanged to find out the position of the scroll view.

I have found this answer, is there an easier solution to this rather than declaring an interface, over ride the scrollview etc as seen on the link I posted?

1

11 Answers 11

50

There is a much easier way than subclassing the ScrollView. The ViewTreeObserver object of the ScrollView can be used to listen for scrolls.

Since the ViewTreeObserver object might change during the lifetime of the ScrollView, we need to register an OnTouchListener on the ScrollView to get it's ViewTreeObserver at the time of scroll.

final ViewTreeObserver.OnScrollChangedListener onScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { //do stuff here } }; final ScrollView scrollView = (ScrollView) findViewById(R.id.scroller); scrollView.setOnTouchListener(new View.OnTouchListener() { private ViewTreeObserver observer; @Override public boolean onTouch(View v, MotionEvent event) { if (observer == null) { observer = scrollView.getViewTreeObserver(); observer.addOnScrollChangedListener(onScrollChangedListener); } else if (!observer.isAlive()) { observer.removeOnScrollChangedListener(onScrollChangedListener); observer = scrollView.getViewTreeObserver(); observer.addOnScrollChangedListener(onScrollChangedListener); } return false; } }); 
Sign up to request clarification or add additional context in comments.

6 Comments

That's not necessarily easier than subclassing ScrollView. It's actually more code. If you subclass you can just override the onScrollChanged method.
I tried this and it seems to fire the event 20 times or more for the same scroll Y value. It doesn't seem ideal.
Well... there is a huge problem with this code! And this issue is resulting in errors like the one from @RandomEngy comment. Therefore I don't know why it got upvoted so high. It adds this listener ON EVERY TOUCH EVENT ON SCROLL. So basically if you click your scroll 20 times it will result in 20 calls to listener. The Listener should just be added once. For example like this: scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { //do stuff }});. NOT INSIDE ON TOUCH METHOD
or even worse it will probably add AT LEAST 40 listeners (at least TOUCH_DOWN and TOUCH_UP for every click) in the example.
I had OnTouchListener because the documentation says "The returned ViewTreeObserver observer is not guaranteed to remain valid for the lifetime of this View". I've edited the answer so that if the ViewTreeObserver isn't alive anymore, the callback to it is removed and added to View's current ViewtreeObserver.
|
15

No.

ScrollView doesn't provide a listener for scroll events or even a way to check how far down the user has scrolled, so you have to do what is suggested by the link.

3 Comments

There is a method onScrollChanged that can be overridden. Likelwise getScrollX, getScrollY can be used.
Seems this answer is invalid now. Starting from API 23 (M) there is a listener called "OnScrollChangeListener" scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { //work with parameters } });
@S.D.NChanakaFernando It should also be noted that it still does not give a built in solution for specifics when it comes to scrolling. For example, if you want to detect the end of a scroll, you will have to do that manually using onScrollChangeListener, which is super annoying. Here are some suggestions of anybody stumbles over this problem: stackoverflow.com/questions/8181828/…
10

This question is fairly old, but in case somebody drops by (like me):

Starting with API 23, Android's View has a OnScrollChangeListener and the matching setter.

The NestedScrollView from the Support library also supports setting a scroll listener even before that API level. As far as I know, NestedScrollView can be used as a replacement for the normal ScrollView without any problems.

5 Comments

there is no backward compatibility tho
@define well, there is NestedScrollView in the Support lib, you csn use that insead of the regular Scrollview without problems
Sorry, for some reason I missed the part where you mention it.. Thanks anyway
@define I simply didn't mention that you can use the NestedScrollView instead of the normal one. I'll add it to the answer.
This is great. We support API 18+ and I really wanted this stock functionality. Thanks!
8

Actually there is a way to know how far the user has scrolled. The method getScrollY() from ScrollView tells you that.

3 Comments

I used this solution in a project in my company. I test everything before i post something here. Maybe you should do the same?
Sorry, misread the question. It works for a ScrollView but not a ListView or a GridView.
3

you can use this code to detect is up or down scroll

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { int lastScroll=0; @Override public void onScrollChanged() { int scrollY = scrollView.getScrollY(); // For ScrollView herzontial use getScrollX() if (scrollY > lastScroll ) { Log.e("scroll","down scroll"+scrollY); } else if (scrollY < lastScroll ) { Log.e("scroll","up scroll"+scrollY); } lastScroll=scrollY; } }); 

Comments

2

I extended my scrollView. This link may help.

class MyScroll extends ScrollView { boolean onTop=true; @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { //Log.d(TAG, "scroll changed: " + this.getTop() + " "+t); if(t <= 0){ onTop = true; //Log.d(TAG, "scroll top: " + t); super.onScrollChanged(l, t, oldl, oldt); return; // reaches the top end } onTop = false; View view = (View) getChildAt(getChildCount()-1); int diff = (view.getBottom()-(getHeight()+getScrollY()+view.getTop()));// Calculate the scrolldiff if( diff <= 0 ){ // if diff is zero, then the bottom has been reached } super.onScrollChanged(l, t, oldl, oldt); } } 

Comments

2

you can use this trigger to show Toast or Start Activity

 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { // do something when Scroll } }); 

Comments

1

NestedScrollView is just like android.widget.ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android.

1st - Use NestedScrollView binding instead ScrollView

2nd - Set Scroll listener, like this, to detect axis Y moving for a headerView by example

 nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { Log.d(TAG, "onScrollChangeForY - scrollY: " + scrollY + " oldScrollY: " + oldScrollY); int MOVE = -1, SCROLL_UP = 0, SCROLL_DOWN = 1; float initialPositionY = headerView.getY(); MOVE = scrollY > oldScrollY ? SCROLL_UP : SCROLL_DOWN; if (MOVE == SCROLL_UP) { int incrementY = scrollY - oldScrollY; headerView.setY(initialPositionY - incrementY); } else { int incrementY = oldScrollY - scrollY; headerView.setY(initialPositionY + incrementY); } } }); 

Comments

0

There is ready for use component, which helps to listen to scroll events of arbitrary views in Android. Internally this component adds ViewTreeObserver scroll events listener on devices with old Android API (similar as proposed in Shubhadeep Chaudhuri's answer) and View scroll events listener on devices with new Android API (API level 23+).

Comments

0

Just use NestedScroll and NestedScroll.setOnScrollChangeListener().

Comments

-1

Starting from API 23 Android M, you can use OnScrollChangeListener.

 scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { //work with parameters } }); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.