Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sync Two ScrollView

Situation: I have two ScrollView inside of each of two HorizontalScrollView of a TableRow.

Goal: When I touch drag one of ScrollView, another ScrollView must scroll as much. For instance, if I have a list of name on left ScrollView and corresponding phone numbers in right ScrollView, scrolling one ScrollView should not destroy the original bounding between the names and phone numbers.

Can it be implemented by onTouchEvent? If so, how should I implement this(on both or one of the ScrollView)?

Please help me out Android Gurus!

like image 643
YoonSung Avatar asked Aug 19 '10 23:08

YoonSung


People also ask

How do I synchronize scrolling and horizontal scrolling?

Click on “View” in the top bar, then click “Synchronise Vertical Scrolling” and/or “Synchronise Horizontal Scrolling”. Alternatively, you can click the fourteenth and fifteenth icons from the right in the icon bar.

Can I synchronise scrolling between two documents in word?

By default, any moved or cloned tabs can be scrolled independently. However, it is possible to synchronise the scrolling between the two open documents. Both vertical and horizontal scrolling can be synchronised independently or together, although horizontal scrolling is only possible if “Word wrap” is disabled.

Can I synchronise the scrolling of a cloned tab?

A cloned tab will carry across any changes since the last save, and will continue to match future changes too. By default, any moved or cloned tabs can be scrolled independently. However, it is possible to synchronise the scrolling between the two open documents.

What is the difference between scrollsynctype both vertical and horizontal?

Vertical: Scrolling synced only vertical. Both: Scrolling synced both vertical and horizontal. If you set VerticalScrollGroup without setting ScrollSyncType, it will automatically be assigned to ScrollSyncType.Vertical. If the ScrollSyncType was set to ScrollSyncType.Horizontal, it would be updated to ScrollSyncType.Both.


2 Answers

I have a simple solution that works for me:

  • subclass both ScrollViews and override their onScrollChanged event to update ScrollManager when scrolling changes:

    public interface ScrollNotifier {   
        public void setScrollListener(ScrollListener scrollListener);
    
        public ScrollListener getScrollListener();
    }
    
    public class SyncedScrollView extends ScrollView implements ScrollNotifier {
    
        //...
    
        private ScrollListener scrollListener = null;
    
        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            if (scrollListener != null)
                scrollListener.onScrollChanged(this, l, t, oldl, oldt);
        }
        @Override
        public void setScrollListener(ScrollListener scrollListener) {
            this.scrollListener = scrollListener;
        }
        @Override
        public ScrollListener getScrollListener() {
            return scrollListener;
        }
    }
    
  • create a ScrollManager class that coordinates the scrolling of multiple participants

    public interface ScrollListener {
        void onScrollChanged(View syncedScrollView, int l, int t, int oldl,
            int oldt);
    }
    
    public class ScrollManager implements ScrollListener {
        private static final int SCROLL_HORIZONTAL = 1;
        private static final int SCROLL_VERTICAL = 2;
    
        private ArrayList<ScrollNotifier> clients = new ArrayList<ScrollNotifier>(4);
    
        private volatile boolean isSyncing = false;
        private int scrollType = SCROLL_HORIZONTAL;
    
        public void addScrollClient(ScrollNotifier client) {
            clients.add(client);
            client.setScrollListener(this);
        }
    
        // TODO fix dependency on all views being of equal horizontal/ vertical
        // dimensions
        @Override
        public void onScrollChanged(View sender, int l, int t, int oldl, int oldt) {
            // avoid notifications while scroll bars are being synchronized
            if (isSyncing) {
                return;
            }
    
            isSyncing = true;
    
            // remember scroll type
            if (l != oldl) {
                scrollType = SCROLL_HORIZONTAL;
            } else if (t != oldt) {
                scrollType = SCROLL_VERTICAL;
            } else {
                // not sure why this should happen
                isSyncing = false;
                return;
            }
    
            // update clients
            for (ScrollNotifier client : clients) {
                View view = (View) client;
                // don't update sender
                if (view == sender) {
                    continue;
                }
    
                // scroll relevant views only
                // TODO Add support for horizontal ListViews - currently weird things happen when ListView is being scrolled horizontally
                if ((scrollType == SCROLL_HORIZONTAL && view instanceof HorizontalScrollView)
                        || (scrollType == SCROLL_VERTICAL && view instanceof ScrollView)
                        || (scrollType == SCROLL_VERTICAL && view instanceof ListView)) {
                    view.scrollTo(l, t);
                }
            }
    
            isSyncing = false;
        }
    }
    
  • create the custom ScrollViews and set the ScrollManager for notification on both

    private void setupScrolling() {
        ScrollNotifier view;
        ScrollManager scrollManager = new ScrollManager();
    
        // timeline horizontal scroller
        view = (ScrollNotifier) findViewById(R.id.epgtimeline_container);
        scrollManager.addScrollClient(view);
    
        // services vertical scroller
        view = (ScrollNotifier) findViewById(R.id.epgservices_container);
        scrollManager.addScrollClient(view);
    
        // content area scrollers
        view = (ScrollNotifier) findViewById(R.id.epgevents_container_inner);
        scrollManager.addScrollClient(view);
        view = (ScrollNotifier) findViewById(R.id.epgevents_container_outer);
        scrollManager.addScrollClient(view);
    }
    
like image 58
andig Avatar answered Oct 15 '22 15:10

andig


Thanks to andig for such a great synching scrollview solution.

But there is little bit lag between both scrollviews while flinging.

So i am writing here extended solution to remove that lag between both scrollview.

I have just used OverScroller class and handled fling event manually in SyncedScrollView.

You have to just replace SyncedScrollView with below code. Use other classes from andig's solution

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.OverScroller;
import android.widget.ScrollView;

/**
 * Created by mitul.varmora on 11/7/2016.
 * SyncedScrollView
 * https://stackoverflow.com/questions/3527119/sync-two-scrollview
 */
public class SyncedScrollView extends ScrollView implements ScrollNotifier {

    private ScrollListener scrollListener = null;

    private OverScroller scroller;
    private Runnable scrollerTaskRunnable;

    public SyncedScrollView(Context context) {
        super(context);
        init();
    }

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

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

    private void init() {

        scroller = new OverScroller(getContext());
        scrollerTaskRunnable = new Runnable() {
            @Override
            public void run() {
                scroller.computeScrollOffset();

                smoothScrollTo(0, scroller.getCurrY());

                if (!scroller.isFinished()) {
                    SyncedScrollView.this.post(this);
                } else {
                    //deceleration ends here, do your code
                    ViewCompat.postInvalidateOnAnimation(SyncedScrollView.this);
                }
            }
        };
    }

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

    @Override
    public ScrollListener getScrollListener() {
        return scrollListener;
    }

    @Override
    public void setScrollListener(ScrollListener scrollListener) {
        this.scrollListener = scrollListener;
    }

    @Override
    public void fling(int velocityY) {

        scroller.forceFinished(true);
        scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, getChildAt(0).getHeight());
        post(scrollerTaskRunnable);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
//        return super.onTouchEvent(ev);
        boolean eventConsumed = super.onTouchEvent(ev);
        if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) {
            if (scroller.isFinished()) {
                //do your code
            }
        }
        return eventConsumed;
    }
}
like image 33
Mitul Varmora Avatar answered Oct 15 '22 15:10

Mitul Varmora