Skip to content Skip to sidebar Skip to footer

How To Have A Parallax Effect Between 2 Viewpagers?

Background Consider the following scenario: There are 2 viewPagers, each of different width and height, but both have the exact same number of pages. dragging on one should drag t

Solution 1:

A possible solution

After a lot of work, we've found a solution that almost has no bugs. There is a rare bug that while scrolling, the other viewPager flashes. Weird thing is that it happens only when the user scrolls the second viewPager. All other tries had other issues, like empty page or "jumpy"/"rubber" effect when finishing the scrolling to a new page.

Here's the code:

privatestaticclassParallaxOnPageChangeListenerimplementsViewPager.OnPageChangeListener {
    privatefinal AtomicReference<ViewPager> masterRef;
    /**
     * the viewpager that is being scrolled
     */private ViewPager viewPager;
    /**
     * the viewpager that should be synced
     */private ViewPager viewPager2;
    privatefloat lastRemainder;
    privateintmLastPos= -1;

    publicParallaxOnPageChangeListener(ViewPager viewPager, ViewPager viewPager2, final AtomicReference<ViewPager> masterRef) {
        this.viewPager = viewPager;
        this.viewPager2 = viewPager2;
        this.masterRef = masterRef;
    }

    @OverridepublicvoidonPageScrollStateChanged(int state) {
        finalViewPagercurrentMaster= masterRef.get();
        if (currentMaster == viewPager2)
            return;
        switch (state) {
            case ViewPager.SCROLL_STATE_DRAGGING:
                if (currentMaster == null)
                    masterRef.set(viewPager);
                break;
            case ViewPager.SCROLL_STATE_SETTLING:
                if (mLastPos != viewPager2.getCurrentItem())
                    viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
                break;
            case ViewPager.SCROLL_STATE_IDLE:
                masterRef.set(null);
                viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
                mLastPos = -1;
                break;
        }
    }

    @OverridepublicvoidonPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (masterRef.get() == viewPager2)
            return;
        if (mLastPos == -1)
            mLastPos = position;
        floatdiffFactor= (float) viewPager2.getWidth() / this.viewPager.getWidth();
        floatscrollTo=this.viewPager.getScrollX() * diffFactor + lastRemainder;
        intscrollToInt= scrollTo < 0 ? (int) Math.ceil(scrollTo) : (int) Math.floor(scrollTo);
        lastRemainder = scrollToInt - scrollTo;
        if (mLastPos != viewPager.getCurrentItem())
            viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
        viewPager2.scrollTo(scrollToInt, 0);

    }

    @OverridepublicvoidonPageSelected(int position) {
    }
}

usage:

/**the current master viewPager*/
    AtomicReference<ViewPager> masterRef = new AtomicReference<>();
    viewPager.addOnPageChangeListener(newParallaxOnPageChangeListener(viewPager, viewPager2, masterRef));
    viewPager2.addOnPageChangeListener(newParallaxOnPageChangeListener(viewPager2, viewPager, masterRef));

For the auto-switching, the original code works fine.

Github Project

Since it's quite hard to test it, and I want to make it easier for you guys to try it out, here's a Github repo:

[https://github.com/AndroidDeveloperLB/ParallaxViewPagers][4]

Do note that as I've mentioned, it still has issues. Mainly the "flashing" effect from time to time, but for some reason, only when scrolling on the second ViewPager.

Solution 2:

I don't think 2 ViewPagers are apt for this. Both encapsulate their own gesture and animation logic, and were not built to be synchronized externally.

You should have a look at the CoordinatorLayout. It is designed specifically to co-ordinate transitions and animations of its child views. Each child view can have a Behaviour and can monitor changes to its dependent sibling views and update its own state.

Solution 3:

Report the first ViewPager's scroll changes to the second ViewPager:

viewPager.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        viewPager2.scrollTo(viewPager.getScrollX(), viewPager2.getScrollY());
    }
});

With complex pages, this method might make the second pager lag behind.


Edit:

Instead of waiting for the scroll to change, you can report to the method calls directly with a custom class:

publicclassSyncedViewPagerextendsViewPager {
    ...
    private ViewPager mPager;

    publicvoidsetSecondViewPager(ViewPager viewPager) {
        mPager = viewPager;
    }

    @OverridepublicvoidscrollBy(int x, int y) {
        super.scrollBy(x, y);
        if (mPager != null) {
            mPager.scrollBy(x, mPager.getScrollY());
        }
    }

    @OverridepublicvoidscrollTo(int x, int y) {
        super.scrollTo(x, y);
        if (mPager != null) {
            mPager.scrollTo(x, mPager.getScrollY());
        }
    }
}

And set the pager.

viewPager.setSecondViewPager(viewPager2);

Note: neither of these methods will invoke the page change listeners on the second pager.You can just add a listener to the first one and account for both pagers there.

Solution 4:

You can use Paralloid. I have an application with parallex effect. Not a pager but a horizontalschrollbar which is quite the same in your context. This library runs well. Alternatively use parallaxviewpager which I didn't use but looks more towards your direction.

Post a Comment for "How To Have A Parallax Effect Between 2 Viewpagers?"