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!
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.
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.
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.
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.
I have a simple solution that works for me:
subclass both ScrollView
s 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 ScrollView
s 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);
}
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With