97

I have 2 ScrollViews in my android layout. How can I synchronise their scroll positions?

Tim
  • 1,230
  • 1
  • 12
  • 11

4 Answers4

292

There is a method in ScrollView...

protected void onScrollChanged(int x, int y, int oldx, int oldy)

Unfortunately Google never thought that we would need to access it, which is why they made it protected and didn't add a "setOnScrollChangedListener" hook. So we will have to do that for ourselves.

First we need an interface.

package com.test;

public interface ScrollViewListener {

    void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);

}

Then we need to override the ScrollView class, to provide the ScrollViewListener hook.

package com.test;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

public class ObservableScrollView extends ScrollView {

    private ScrollViewListener scrollViewListener = null;

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

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

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

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if(scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }

}

And we should specify this new ObservableScrollView class in the layout, instead of the existing ScrollView tags.

<com.test.ObservableScrollView
    android:id="@+id/scrollview1"
    ... >

    ...

</com.test.ObservableScrollView>

Finally, we put it all together in the Layout class.

package com.test;

import android.app.Activity;
import android.os.Bundle;

public class Q3948934 extends Activity implements ScrollViewListener {

    private ObservableScrollView scrollView1 = null;
    private ObservableScrollView scrollView2 = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q3948934);

        scrollView1 = (ObservableScrollView) findViewById(R.id.scrollview1);
        scrollView1.setScrollViewListener(this);
        scrollView2 = (ObservableScrollView) findViewById(R.id.scrollview2);
        scrollView2.setScrollViewListener(this);
    }

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
        if(scrollView == scrollView1) {
            scrollView2.scrollTo(x, y);
        } else if(scrollView == scrollView2) {
            scrollView1.scrollTo(x, y);
        }
    }

}

The scrollTo() code takes care of any loop conditions for us, so we don't need to worry about that. The only caveat is that this solution is not guaranteed to work in future versions of Android, because we are overriding a protected method.

Andy
  • 4,039
  • 1
  • 20
  • 15
  • -Andy I am also using the code but it is throwing me null pointer expection can you help me – hemant Nov 26 '12 at 13:11
  • @hemant Can you please point out in which line the NullPointer is thrown and what Android version you are working with? – sulai Nov 29 '12 at 10:15
  • Hi Andy,thanks for the code.It works great.I would like to know one thing [if you or any1 has ans. to this]- When I fling my scrollView, the onscrollchanged method does not registers all the X and Y coordinates while the scrollview moves. It only gives me the starting position and the last position. Say for ex- I start the fling from Y=10 and leave at Y=30 and then fling velocity takes it to Y = 50 and then stops. So onscrollchanged only registers-perhaps 10, 11, 12..30 and then 49, 50.How can I make it register all the intermediate locations as well and get all the coordinates from 10 to 50?? – alchemist Sep 12 '13 at 14:37
  • @Andy I thought `protected` methods were meant to be overridden.. otherwise just use package private or private. – dcow Nov 14 '13 at 08:56
  • @Andy hey i have implemented this.its works.but one problem occur.while i come on screen and scroll the data and while i press the second button in this screen i never come on last position of 1 screen .mean i have to press the button and then we got the solution.any idea? – Google Feb 18 '14 at 06:15
  • @Innovator-z You provide the ScrollViewListener class yourself, using the code above. – Andy Apr 10 '14 at 09:41
  • @dcow You're right, but I posted the solution in Oct 2010 when Google were still making big changes to the Android API every six months. I should have clarified that I wasn't talking about protected methods in general. – Andy Apr 10 '14 at 09:50
  • @Google There aren't any buttons in the example, so I'm not sure what your buttons are doing. Can you create a very simple example class that demonstrates the exact problem you have? – Andy Apr 10 '14 at 09:56
  • @Andy I want to know one thing.. how do you get to know the direction of the scroll? i mean wheather it's upward or downward – silverFoxA May 22 '15 at 11:28
  • @Andy I am using this `Log.d("scroll",y+":"+oldy);` to see value up to which `y` is scrolled but in *Logs*, every value of `y` is not there i.e. `y = 149` and then directly to `y = 151` ...Can you please tell why is it missing some values? I need to run some code which runs on every 50 units scrolled – Dhruvam Gupta Nov 06 '17 at 11:43
12

An improvement to Andy's solution : In his code, he uses scrollTo, the issue is, if you fling one scrollview in one direction and then fling another one in another direction, you'll notice that the first one doesn't stop his previous fling movement.

This is due to the fact that scrollView uses computeScroll() to do it's flinging gestures, and it enters in conflict with scrollTo.

In order to prevent this, just program the onScrollChanged this way :

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
    if(interceptScroll){
        interceptScroll=false;
        if(scrollView == scrollView1) {
            scrollView2.onOverScrolled(x,y,true,true);
        } else if(scrollView == scrollView2) {
            scrollView1.onOverScrolled(x,y,true,true);
        }
        interceptScroll=true;
    }
}

with interceptScroll a static boolean initialized to true. (this helps avoid infinite loops on ScrollChanged)

onOverScrolled is the only function I found that could be used to stop the scrollView from flinging (but there might be others I've missed !)

In order to access this function (which is protected) you have to add this to your ObservableScrollViewer

public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
Taiko
  • 1,351
  • 1
  • 19
  • 35
5

Why not just implements OnTouchListener in your activity. Then override the onTouch method, then get the scroll postion of the first ScrollViewOne.getScrollY() and update ScrollViewTwo.scrollTo(0, ScrollViewOne.getScrollY());

Just another idea... :)

Taryn
  • 242,637
  • 56
  • 362
  • 405
Aaron Newton
  • 49
  • 1
  • 2
  • 7
    This can work but when you touch and let go the scroll view can scroll for a bit more after you let go which is not captured. – richy Apr 20 '12 at 03:25
  • How can i refer / get the refrence of the scrollView ScrollViewOne in this case in java ? – Bunny Rabbit Sep 22 '12 at 11:19
  • There are also other ways to scroll (onTrackballEvent() for example) – surfealokesea Apr 16 '13 at 15:11
  • It is a very good idea but instead of getting and setting the scroll y position, it is easier to just dispatch the touch event to the second scrollview. This will also keep the "fling" going in the second scrollview. MotionEvent newEvent = MotionEvent.obtain(event); documentsScrollView.dispatchTouchEvent(newEvent); – Eduard Mossinkoff May 11 '14 at 10:17
3

In the Android support-v4 package, Android provide a new class named NestedScrollView.

we can replace the <ScrollView> node with <android.support.v4.widget.NestedScrollView> in layout xml, and implements its NestedScrollView.OnScrollChangeListener in Java to handle the scrolling.

That makes things easier.

wangzhangjian
  • 296
  • 1
  • 5