Synchronise ScrollView scroll positions - android Synchronise ScrollView scroll positions - android android android

Synchronise ScrollView scroll positions - android


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.


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


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... :)