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

时间:2015-06-30 12:42:18

标签: android android-viewpager parallax

背景

考虑以下情况:

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

    我认为我的某些功能正常运行,但它存在一些问题:

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

      mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mViewPager.getAdapter().getCount(), true);
      
    5. 我尝试了什么

      我已经尝试了this post,但它并没有很好地运作,因为当用户拖动在完成切换到另一个页面之前停止时会出现相同的问题。

      只有类似的解决方案可以使用单个ViewPager(here),但我需要使用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) {
              }
          });
      

      我选择使用假拖动,因为even the docs说它可能对这种完全相同的情况有用:

        

      如果要同步动作,假拖动可能很有用   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;
          }
      }
      

      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>
      

      问题

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

4 个答案:

答案 0 :(得分:5)

可能的解决方案

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

以下是代码:

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));

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

Github项目

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

[<强> https://github.com/AndroidDeveloperLB/ParallaxViewPagers ] [4]

请注意,正如我所提到的,它仍有问题。主要是&#34;闪烁&#34;不时有效,但出于某种原因,只有在第二个ViewPager上滚动时才会出现。

答案 1 :(得分:1)

我不认为2 Viewbook很适合这个。两者都封装了自己的手势和动画逻辑,并没有构建为外部同步。

你应该看一下CoordinatorLayout。它专门用于协调其子视图的过渡和动画。每个子视图都可以有Behaviour,并且可以监视其依赖的兄弟视图的更改并更新其自己的状态。

答案 2 :(得分:1)

将第一个ViewPager的滚动更改报告给第二个ViewPager

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

对于复杂页面,此方法可能使第二个寻呼机滞后。

编辑:

您可以直接使用自定义类报告方法调用,而不是等待滚动更改:

public class SyncedViewPager extends ViewPager {
    ...
    private ViewPager mPager;

    public void setSecondViewPager(ViewPager viewPager) {
        mPager = viewPager;
    }

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

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

设置寻呼机。

viewPager.setSecondViewPager(viewPager2);

注意:这些方法都不会在第二个寻呼机上调用页面更改侦听器。
您只需在第一个寻呼机上添加一个侦听器,并在那里为两个寻呼机负责。

答案 3 :(得分:0)

您可以使用Paralloid。我有一个parallex效果的应用程序。不是寻呼机,而是水平线滚动条,在您的上下文中完全相同。这个库运行良好。或者使用我没有使用的parallaxviewpager,但看起来更倾向于你的方向。