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

and*_*per 15 android parallax android-viewpager

背景

请考虑以下情形:

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

问题

我认为我有一些功能,但它有一些问题:

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

    mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mViewPager.getAdapter().getCount(), true);
    
    Run Code Online (Sandbox Code Playgroud)

我试过的

我已经尝试过这篇文章了,但是效果不好,因为当用户拖动到完成切换到另一个页面之前停止时它会出现同样的问题.

只有类似的解决方案可以使用单个ViewPager(此处),但我需要使用2个ViewPagers,每个都会影响另一个.

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

    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) {
        }
    });
Run Code Online (Sandbox Code Playgroud)

我选择使用假拖动,因为即使文档说它对这个完全相同的场景也很有用:

如果要将ViewPager的运动与另一个视图的触摸滚动同步,同时仍允许ViewPager控制捕捉动作和拖动行为,则假拖动可能很有用.(例如,视差滚动选项卡.)调用fakeDragBy(float)来模拟实际的拖动动作.调用endFakeDrag()以根据需要完成假拖动和拖拽.

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

这个问题

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

and*_*per 5

可能的解决方案

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

这是代码:

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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

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

Github项目

由于它很难测试,我想让你们更容易尝试一下,这里是一个Github回购:

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

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