Reputation: 3288
Is it possible to have 2 ViewPagers
that simultaniously scroll together, if I start scrolling on one, the other does the exact same scrolling behaviour. Or should I implement somthing other than a ViewPager.
thank you
Upvotes: 2
Views: 2208
Reputation: 45052
Drop this class in your project
import androidx.viewpager.widget.ViewPager;
/**
* Sync Scroll 2 View Pager
*/
public class SyncScrollOnTouchListener implements ViewPager.OnPageChangeListener {
private ViewPager master;
private ViewPager slave;
private int mScrollState = ViewPager.SCROLL_STATE_IDLE;
public SyncScrollOnTouchListener(ViewPager master, ViewPager slave){
this.master = master;
this.slave = slave;
}
@Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
return;
}
this.slave.scrollTo(this.master.getScrollX()*
this.slave.getWidth()/
this.master.getWidth(), 0);
}
@Override
public void onPageSelected(final int position) {
}
@Override
public void onPageScrollStateChanged(final int state) {
mScrollState = state;
if (state == ViewPager.SCROLL_STATE_IDLE) {
this.slave.setCurrentItem(this.master
.getCurrentItem(), false);
}
}
}
Usage:
Now you can sync page change of View Pages
masterPager.addOnPageChangeListener(newSyncScrollOnTouchListener(masterPager,slavePager));
//Sync Scroll of Pages
leftPanelPager.addOnPageChangeListener(new SyncScrollOnTouchListener(leftPanelPager,rightPanelPager));
rightPanelPager.addOnPageChangeListener(new SyncScrollOnTouchListener(rightPanelPager,leftPanelPager));
Upvotes: 0
Reputation: 1125
The solution that worked best for me was to pass MotionEvent
in OnTouchListener
between ViewPager
instances. Tried fake dragging but it was always laggy and buggy (tried the solution from this thread too - didn't work) - I needed a smooth, parallax-like effect.
So, my advice is to implement a View.OnTouchListener
. The MotionEvent
has to be scaled to compensate for the difference in width.
public class SyncScrollOnTouchListener implements View.OnTouchListener {
private final View syncedView;
public SyncScrollOnTouchListener(@NonNull View syncedView) {
this.syncedView = syncedView;
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
MotionEvent syncEvent = MotionEvent.obtain(motionEvent);
float width1 = view.getWidth();
float width2 = syncedView.getWidth();
//sync motion of two view pagers by simulating a touch event
//offset by its X position, and scaled by width ratio
syncEvent.setLocation(syncedView.getX() + motionEvent.getX() * width2 / width1,
motionEvent.getY());
syncedView.onTouchEvent(syncEvent);
return false;
}
}
Then set it to your ViewPager
sourcePager.setOnTouchListener(new SyncScrollOnTouchListener(targetPager));
Note that this solution will only work if both pagers have the same orientation. If you need it to work for different orientations - adjust syncEvent
Y coordinate instead of X.
There is one more issue that we need to take into account - minimum fling speed and distance that can cause just one pager to change page.
It can be easily fixed by adding an OnPageChangeListener
to our pager
sourcePager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
//no-op
}
@Override
public void onPageSelected(int position) {
targetPager.setCurrentItem(position, true);
}
@Override
public void onPageScrollStateChanged(int state) {
//no-op
}
});
Upvotes: 4
Reputation: 38585
You can give each an OnPageChangeListsner
and implement onPageScrolled
(and maybe also onPageSelected
for when you change pages without scrolling). Since the logic will be the same, we can write a class for this:
public class ViewPagerScrollSync implements ViewPager.OnPageChangeListener {
private ViewPager actor; // the one being scrolled
private ViewPager target; // the one that needs to be scrolled in sync
public ViewPagerScrollSync(ViewPager actor, ViewPager target) {
this.actor = actor;
this.target = target;
actor.setOnPageChangeListener(this);
}
@Override
public void onPageScrollStateChanged(int state) {
if (actor.isFakeDragging()) {
return;
}
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
// actor has begun a drag
target.beginFakeDrag();
} else if (state == ViewPager.SCROLL_STATE_IDLE) {
// actor has finished settling
target.endFakeDrag();
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (actor.isFakeDragging()) {
return;
}
if (target.isFakeDragging()) {
// calculate drag amount in pixels.
// i don't have code for this off the top of my head, but you'll probably
// have to store the last position and offset from the previous call to
// this method and take the difference.
float dx = ...
target.fakeDragBy(dx);
}
}
@Override
public void onPageSelected(int position) {
if (actor.isFakeDragging()) {
return;
}
// Check isFakeDragging here because this callback also occurs when
// the user lifts his finger on a drag. If it was a real drag, we will
// have begun a fake drag of the target; otherwise it was probably a
// programmatic change of the current page.
if (!target.isFakeDragging()) {
target.setCurrentItem(position);
}
}
}
Then in your Activity/Fragment, you would do this:
ViewPager pager1 = ...
ViewPager pager2 = ...
ViewPagerScrollSync sync1 = new ViewPagerScrollSync(pager1, pager2);
ViewPagerScrollSync sync2 = new ViewPagerScrollSync(pager2, pager1);
Upvotes: 3
Reputation: 11608
you will need to keep an eye on your positions to avoid IndexOutOfBounds
errors. Generally:
viewPager1.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
viewPager2.setCurrentItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Auto-generated method stub
}
@Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
}
});
This will work assuming that both ViewPagers
have the same number of items. Otherwise, you will need to track your positions to synchronize the behavior of both pagers.
To clarify: if your Adapters
have a different number of items or you don't want both pagers to behave exactly the same, you will need to check the position of viewPager1
in the onPageSelected()
method and then adjust the position to pass to the setCurrentItem()
method of viewPager2
Upvotes: 1