14

我正在尝试同步两个ViewPagers,显然在我之前有很多人,而且我已经做到了:

private ViewPager mNavPager;

private ViewPager mMainPager;

private final OnPageChangeListener mNavPagerListener = new OnPageChangeListener() {

    private boolean mNavDragging;
    private int mScrollPosition;

    @Override
    public void onPageSelected(int position) {
        mScrollPosition = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if(mNavDragging)
            mMainPager.scrollTo(positionOffsetPixels, 0);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch(state) {
        case ViewPager.SCROLL_STATE_DRAGGING:
        case ViewPager.SCROLL_STATE_SETTLING:
            mNavDragging = true;
            break;
        case ViewPager.SCROLL_STATE_IDLE:
            mNavDragging = false;
            break;
        }
    }
};

private OnPageChangeListener mMainPagerListener = new OnPageChangeListener() {

    private boolean mMainDragging;
    private int mScrollPosition;

    @Override
    public void onPageSelected(int position) {
        mScrollPosition = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if(mMainDragging)
            mNavPager.scrollTo(positionOffsetPixels, 0);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch(state) {
        case ViewPager.SCROLL_STATE_DRAGGING:
        case ViewPager.SCROLL_STATE_SETTLING:
            mMainDragging = true;
            break;
        case ViewPager.SCROLL_STATE_IDLE:
            mMainDragging = false;
            break;
        }
    }
};

如果其中一个是手动滚动的,则另一个使用滚动状态属性从属于它。它可以很好地工作,直到物品到达最终位置;此时,从属寻呼机立即弹回先前选择的项目,就好像没有发生滚动一样。

我尝试过ViewPager#setCurrentItem(mScrolledPosition)使用各种不同的逻辑约束进行调用,但这也不起作用,尽管它偶尔会使情况变得更糟。我觉得好像必须有什么可以做的,但我不确定是什么。

我怎样才能让从属寻呼机保持在正确的位置?

4

6 回答 6

20

我使用 OnPageChangeListener 以更简单(和更简洁)的方式解决了这个问题:

mViewPager1.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

      private int mScrollState = ViewPager.SCROLL_STATE_IDLE;

      @Override
      public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
          return;
        }
        mViewPager2.scrollTo(mViewPager1.getScrollX(), mViewPager2.getScrollY());
      }

      @Override
      public void onPageSelected(final int position) {

      }

      @Override
      public void onPageScrollStateChanged(final int state) {
        mScrollState = state;
        if (state == ViewPager.SCROLL_STATE_IDLE) {
          mViewPager2.setCurrentItem(mViewPager1.getCurrentItem(), false);
        }
      }
});

mViewPager2.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

      private int mScrollState = ViewPager.SCROLL_STATE_IDLE;

      @Override
      public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
          return;
        }
        mViewPager1.scrollTo(mViewPager2.getScrollX(), mViewPager1.getScrollY());
      }

      @Override
      public void onPageSelected(final int position) {

      }

      @Override
      public void onPageScrollStateChanged(final int state) {
        mScrollState = state;
        if (state == ViewPager.SCROLL_STATE_IDLE) {
          mViewPager1.setCurrentItem(mViewPager2.getCurrentItem(), false);
        }
      }
});
于 2014-10-22T17:15:28.113 回答
7

我在不使用监听器的情况下解决了这个问题。因此,您可以将侦听器用于其他一些东西,并使代码看起来更干净。

我知道这是很久以前提出的问题。我正在寻找解决方案并自己解决了。

这就是我的自定义 ViewPager 代码的方式

public class CustomPager extends ViewPager {
CustomPager mCustomPager;
private boolean forSuper;

public CustomPager(Context context) {
    super(context);
}

public CustomPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
    if (!forSuper) {

        mCustomPager.forSuper(true);
        mCustomPager.onInterceptTouchEvent(arg0);
        mCustomPager.forSuper(false);
    }
    return super.onInterceptTouchEvent(arg0);
}

@Override
public boolean onTouchEvent(MotionEvent arg0) {
    if (!forSuper) {
        mCustomPager.forSuper(true);
        mCustomPager.onTouchEvent(arg0);
        mCustomPager.forSuper(false);
    }
    return super.onTouchEvent(arg0);
}

public void setViewPager(CustomPager customPager) {
    mCustomPager = customPager;
}

public void forSuper(boolean forSuper) {
    this.forSuper = forSuper;
}

@Override
public void setCurrentItem(int item, boolean smoothScroll) {
    if (!forSuper) {
        mCustomPager.forSuper(true);
        mCustomPager.setCurrentItem(item, smoothScroll);
        mCustomPager.forSuper(false);
    }
    super.setCurrentItem(item, smoothScroll);
}

@Override
public void setCurrentItem(int item) {
    if (!forSuper) {
        mCustomPager.forSuper(true);
        mCustomPager.setCurrentItem(item);
        mCustomPager.forSuper(false);
    }
    super.setCurrentItem(item);

}

}

并且您必须在设置适配器之前以及在使用 ID 获取参考之后设置这样的寻呼机。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    backPager=(CustomPager) findViewById(R.id.BackPager);
    frontPager = (CustomPager) findViewById(R.id.frontPager);
    frontPager.setViewPager(backPager);
    backPager.setViewPager(frontPager);
    backPager.setAdapter(your Adapter);
    frontPager.setAdapter(your Adapter);
}

必须在分配适配器之前设置 ViewPagers。

于 2014-06-08T09:25:38.477 回答
4

我有一个类似的问题,还需要将我的同步与PageTransformer.

最初在这里发布了这个答案:https ://stackoverflow.com/a/43638796/2867886

但为方便起见,我将其粘贴在这里。

最适合我的解决方案是MotionEvent在实例OnTouchListener之间传递。ViewPager尝试过假拖动,但它总是很慢而且有问题——我需要一个平滑的、类似视差的效果。

所以,我的建议是实施一个View.OnTouchListener. MotionEvent必须缩放以补偿宽度的差异。

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

然后将其设置为您的ViewPager

    sourcePager.setOnTouchListener(new SyncScrollOnTouchListener(targetPager));

请注意,此解决方案仅在两个寻呼机具有相同方向时才有效。如果您需要它适用于不同的方向 - 调整syncEventY 坐标而不是 X。

我们还需要考虑一个问题——最小的投掷速度和距离,这可能会导致只有一个寻呼机改变页面。

它可以通过添加OnPageChangeListener到我们的寻呼机轻松修复

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
        }
    }); 
于 2017-05-04T11:07:51.207 回答
2

这一切都正确,除了它有时会错过从属视图上的非常快速的轻弹。但是,出于某种原因,包括在稳定阶段的虚假阻力事件会导致真正的问题。

private ViewPager mNavPager;

private ViewPager mMainPager;

private final OnPageChangeListener mNavPagerListener = new OnPageChangeListener() {

    private int mLastScrollPosition;
    private int mLastPagePosition;

    @Override
    public void onPageSelected(int position) {
        mLastPagePosition = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if(mMainPager.isFakeDragging()) {
            int absoluteOffsetPixels = positionOffsetPixels;
            if(mLastPagePosition!=position) {
                absoluteOffsetPixels += (position - mLastPagePosition) * mMainPager.getWidth();
                mLastPagePosition = position;
            }
            Log.d(TAG, "fake nav drag by " + (mLastScrollPosition - absoluteOffsetPixels));
            mMainPager.fakeDragBy(mLastScrollPosition - absoluteOffsetPixels);
            mLastScrollPosition = positionOffsetPixels;
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(!mNavPager.isFakeDragging()) {
            switch(state) {
            case ViewPager.SCROLL_STATE_DRAGGING:
                if(!mMainPager.isFakeDragging())
                    mMainPager.beginFakeDrag();
                break;
            case ViewPager.SCROLL_STATE_SETTLING:
            case ViewPager.SCROLL_STATE_IDLE:
                if(mMainPager.isFakeDragging()) {
                    mMainPager.endFakeDrag();
                    mLastScrollPosition = 0;
                }
                break;
            }
        }
    }
};

private OnPageChangeListener mMainPagerListener = new OnPageChangeListener() {

    private int mLastScrollPosition;
    private int mLastPagePosition;

    @Override
    public void onPageSelected(int position) {
        mLastPagePosition = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if(mNavPager.isFakeDragging()) {
            int absoluteOffsetPixels = positionOffsetPixels;
            if(mLastPagePosition!=position) {
                absoluteOffsetPixels += (position - mLastPagePosition) * mMainPager.getWidth();
                mLastPagePosition = position;
            }
            Log.d(TAG, "fake nav drag by " + (mLastScrollPosition - absoluteOffsetPixels));
            mNavPager.fakeDragBy(mLastScrollPosition - absoluteOffsetPixels);
            mLastScrollPosition = positionOffsetPixels;
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(!mMainPager.isFakeDragging()) {
            switch(state) {
            case ViewPager.SCROLL_STATE_DRAGGING:
                if(!mNavPager.isFakeDragging())
                    mNavPager.beginFakeDrag();
                break;
            case ViewPager.SCROLL_STATE_SETTLING:
            case ViewPager.SCROLL_STATE_IDLE:
                if(mNavPager.isFakeDragging()) {
                    mNavPager.endFakeDrag();
                    mLastScrollPosition = 0;
                }
                break;
            }
        }
    }
};

编辑

我现在相信如果没有一些相当大量的自定义代码,这是不可能的。原因本质上是两个ViewPagers都有一个VelocityTracker内部,它控制滚动。由于MotionEvent传入的 s 可能不会在VelocityTracker每个寻呼机的相同相对时间传递给 s,因此跟踪器偶尔会就如何反应得出不同的结论。

但是,可以使用 modifiedPagerTitleStrip来精确跟踪 a ViewPager,并将条带捕获的触摸事件直接传输到ViewPager.

的来源在PagerTitleStrip这里。

概括地说,要完成这项工作需要做如下:替换mPrevTextmCurrTextmNextText使用您要使用的类型的视图;删除onAttachedToWindow()andonDetachedFromWindow()函数;PagerAdapter删除对处理数据集观察者的调用,并OnTouchListener在修改后的条带中添加一个假拖动主寻呼机。您还需要将修改后的标题条添加为 an OnPageChangeListenerViewPager因为内部侦听器在包外不可见。

这是巨大的痛苦吗?是的。但它有效。我会尽快把它写得更详细。

于 2013-04-19T14:42:55.317 回答
2

在所有其他答案的帮助下,我创建了一个类,因此可以轻松实现它。

    public class OnPageChangeListernerSync implements ViewPager.OnPageChangeListener {

    private ViewPager master;
    private ViewPager slave;
    private int mScrollState = ViewPager.SCROLL_STATE_IDLE;

    public OnPageChangeListernerSync(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);
        }
    }
}

...

// In your activity
this.upperPager.addOnPageChangeListener(new OnPageChangeListernerSync(this.upperPager, this.lowerPager));

this.lowerPager.addOnPageChangeListener(new OnPageChangeListernerSync(this.lowerPager, this.upperPager));
于 2017-04-04T09:09:32.403 回答
0

在某些情况下,所有答案都大致正确。在这里,我再给出一个答案,用于同时滑动两个 ViewPagers,无论大小是否相同

viewPagerBanner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    private int scrollState = ViewPager.SCROLL_STATE_IDLE;
    // Indicates that the pager is in an idle, settled state. 
    // The current page is fully in view and no animation is in progress.

    @Override
    public void onPageScrolled(int position, float positionOffset, 
                               int positionOffsetPixels) {
        if (scrollState == ViewPager.SCROLL_STATE_IDLE) {
            return;
        }
        viewPagerTitle.scrollTo(viewPagerBanner.getScrollX()*
                                viewPagerTitle.getWidth()/
                                viewPagerBanner.getWidth(), 0);
        // We are not interested in Y axis position
    }

    @Override
    public void onPageSelected(int position) {}

    @Override
    public void onPageScrollStateChanged(int state) {
        scrollState = state;
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            viewPagerTitle.setCurrentItem(viewPagerBanner.getCurrentItem(), false);
        }
    }
});
于 2016-07-11T10:52:17.847 回答