Synchronise ScrollView scroll positions - android

AndroidScroll

Android Problem Overview


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

Android Solutions


Solution 1 - 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.

Solution 2 - Android

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

Solution 3 - Android

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

Solution 4 - Android

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTimView Question on Stackoverflow
Solution 1 - AndroidAndyView Answer on Stackoverflow
Solution 2 - AndroidTaikoView Answer on Stackoverflow
Solution 3 - AndroidAaron NewtonView Answer on Stackoverflow
Solution 4 - AndroidwangzhangjianView Answer on Stackoverflow