如何在 2 个 ViewPager 之间产生视差效果?

How to have a parallax effect between 2 ViewPagers?(如何在 2 个 ViewPager 之间产生视差效果?)

本文介绍了如何在 2 个 ViewPager 之间产生视差效果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下场景:

  1. 有 2 个 viewPager,每个 viewPager 的宽度和高度都不同,但它们的页数完全相同.
  2. 拖动一个应该拖动另一个,所以效果就像视差"效果.
  3. 与 #2 相同,但用于另一个 viewPager.
  4. viewpagers 之间还有一个自动切换机制,因此它会每 2 秒自动切换到下一页(如果在最后一页,则切换回第一页).

问题

我认为我的某些功能可以正常工作,但存在一些问题:

The problem

I think I got some functionality working, but it has some issues:

  1. 这只是简单的 XML.这里没什么特别的.
  2. 我使用了OnPageChangeListener",在onPageScrolled"中,我使用了假拖动.问题是,如果用户的拖动在页面中间附近停止(从而激活了向左/向右自动滚动 viewPager),虚假的拖动会失去同步,并且可能会发生奇怪的事情:错误的页面甚至停留在页面之间...
  3. 目前,我没有通过用户拖动来处理其他 viewPager,但这也可能是个问题.
  4. 当我成功解决#2(或#3)时,这可能是一个问题.目前,我正在使用一个处理程序,该处理程序使用可运行文件并在内部调用此命令:

  1. This is just simple XML. nothing special here.
  2. I've used 'OnPageChangeListener' and there, in 'onPageScrolled' , I've used fake-dragging. Problem is, if the dragging of the user stops near the middle of the page (thus activates auto-scrolling the viewPager left/right), the fake dragging loses its sync, and weird things can occur: wrong page or even staying between pages...
  3. For now, I don't handle the other viewPager with the dragging of the user, but it might be an issue too.
  4. this can be an issue when I succeed solving #2 (or #3). For now, I'm using a handler that use a runnable and calls this command inside :

mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mViewPager.getAdapter().getCount(), true);

我尝试过的

我试过 这篇文章,但效果不佳,因为当用户在完成切换到另一个页面之前停止拖动时,它会遇到同样的问题.

What I've tried

I've tried this post, but it didn't work well, as it got the same issues when the user-dragging stops before finished swithching to another page.

唯一有效的类似解决方案是使用单个 ViewPager(这里),但我需要使用 2 个 ViewPagers,每个 ViewPagers 都会影响另一个.

Only similar solution that works, is with a single ViewPager (here), but I need to work with 2 ViewPagers, each affects the other.

所以我自己尝试过:假设一个 viewPager 是viewPager",另一个(应该遵循viewPager")是viewPager2",这就是我所做的:

So I've tried doing it myself: suppose one viewPager is "viewPager", and the other (that is supposed to follow "viewPager") is "viewPager2" , this is what I've done:

    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        int lastPositionOffsetPixels=Integer.MIN_VALUE;

        @Override
        public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
            if (!viewPager2.isFakeDragging())
                viewPager2.beginFakeDrag();
            if(lastPositionOffsetPixels==Integer.MIN_VALUE) {
                lastPositionOffsetPixels=positionOffsetPixels;
                return;
            }
            viewPager2.fakeDragBy((lastPositionOffsetPixels - positionOffsetPixels) * viewPager2.getWidth() / viewPager.getWidth());
            lastPositionOffsetPixels = positionOffsetPixels;
        }

        @Override
        public void onPageSelected(final int position) {
            if (viewPager2.isFakeDragging())
                viewPager2.endFakeDrag();
            viewPager2.setCurrentItem(position,true);
        }

        @Override
        public void onPageScrollStateChanged(final int state) {
        }
    });

我选择使用假拖动是因为 甚至文档都说它可能对同样的场景有用:

I've chosen to use fake-dragging because even the docs say it could be useful for this exact same scenario :

如果您想同步ViewPager 与另一个视图的触摸滚动,同时仍然让 ViewPager 控制捕捉动作和投掷行为.(例如视差滚动选项卡.)调用 fakeDragBy(float) 来模拟实际的拖动动作.调用 endFakeDrag() 完成假拖动和必要时扔掉.

A fake drag can be useful if you want to synchronize the motion of the ViewPager with the touch scrolling of another view, while still letting the ViewPager control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) Call fakeDragBy(float) to simulate the actual drag motion. Call endFakeDrag() to complete the fake drag and fling as necessary.

为了方便测试,这里有更多代码:

To make it easy to test, here's more code:

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
    final ViewPager viewPager2 = (ViewPager) findViewById(R.id.viewPager2);
    viewPager.setAdapter(new MyPagerAdapter());
    viewPager2.setAdapter(new MyPagerAdapter());
    //do here what's needed to make "viewPager2" to follow dragging on "viewPager"
    }

private class MyPagerAdapter extends PagerAdapter {
    int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff};

    @Override
    public int getCount() {
        return 3;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return (view == object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        TextView textView = new TextView(MainActivity.this);
        textView.setText("item" + position);
        textView.setBackgroundColor(colors[position]);
        textView.setGravity(Gravity.CENTER);
        final LayoutParams params = new LayoutParams();
        params.height = LayoutParams.MATCH_PARENT;
        params.width = LayoutParams.MATCH_PARENT;
        params.gravity = Gravity.CENTER;
        textView.setLayoutParams(params);
        textView.setTextColor(0xff000000);
        container.addView(textView);
        return textView;
    }
}

activity_main

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="viewPager:"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="viewPager2:"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager2"
        android:layout_width="match_parent"
        android:layout_height="100dp"/>

</LinearLayout>

问题

代码几乎运行良好.我只需要知道,为了避免(由用户)停止拖动的问题,缺少什么?其他问题可能有更简单的解决方案.

The question

The code almost works well. I just need to know, what is missing to avoid the issues of stopping the dragging (by the user)? The other issues might have easier solutions.

推荐答案

可能的解决方案

经过大量工作,我们找到了一个几乎没有错误的解决方案.有一个罕见的错误,即在滚动时,另一个 viewPager 会闪烁.奇怪的是,它仅在用户滚动第二个 viewPager 时发生.所有其他尝试都有其他问题,例如在完成滚动到新页面时出现空白页面或跳跃"/橡胶"效果.

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.

代码如下:

private static class ParallaxOnPageChangeListener implements ViewPager.OnPageChangeListener {
    private final AtomicReference<ViewPager> masterRef;
    /**
     * the viewpager that is being scrolled
     */
    private ViewPager viewPager;
    /**
     * the viewpager that should be synced
     */
    private ViewPager viewPager2;
    private float lastRemainder;
    private int mLastPos = -1;

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

    @Override
    public void onPageScrollStateChanged(int state) {
        final ViewPager currentMaster = 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;
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (masterRef.get() == viewPager2)
            return;
        if (mLastPos == -1)
            mLastPos = position;
        float diffFactor = (float) viewPager2.getWidth() / this.viewPager.getWidth();
        float scrollTo = this.viewPager.getScrollX() * diffFactor + lastRemainder;
        int scrollToInt = 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);

    }

    @Override
    public void onPageSelected(int position) {
    }
}

用法:

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

对于自动切换,原代码可以正常工作.

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

由于测试起来相当困难,而且我想让你们更容易尝试,这里有一个 Github repo:

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]

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

请注意,正如我所提到的,它仍然存在问题.主要是不时出现的闪烁"效果,但由于某种原因,只有在第二个ViewPager上滚动时才出现.

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.

这篇关于如何在 2 个 ViewPager 之间产生视差效果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:如何在 2 个 ViewPager 之间产生视差效果?