调用setCurrentItem(0)时,不会触发onPageSelected事件。

69

我有一个包含ViewPager的Activity,用于显示一堆图片。当它启动时,ViewPager的位置是基于用户在前一个Activity中选择的内容设置的,类似于一个画廊。

我希望每次选择新页面时都会调用onPageSelected方法,即当Activity首次打开或用户滑动到新页面时。

我通过以下方式设置起始点:

 mPager.setCurrentItem(index);

除了当setCurrentItem调用的索引设置为0时,这样将不会触发onPageSelected函数,其他都正常工作。

mPager.setOnPageChangeListener(new OnPageChangeListener() {
  @Override
  public void onPageSelected(int index) {
    Log.d(TAG, "onPageSelected " + index);
  }
  ...
}

所以我的问题是:这是一个bug吗?如果是,我该怎么办呢?

10个回答

92
到目前为止,我发现最干净的解决方案是获取您在ViewPager上设置的onPageChangeListener的引用(因为我不认为有一个ViewPager.getOnPageChangeListener()方法),然后在设置ViewPager的适配器之后调用:
onPageChangeListener.onPageSelected(viewPager.getCurrentItem());

然而,当前索引页面的片段尚未实例化(至少如果你使用FragmentStatePagerAdapter),因此你可能需要将其包装在一个可运行对象中,例如:

viewPager.post(new Runnable(){
@Override
    public void run() {
        onPageChangeListener.onPageSelected(viewPager.getCurrentItem());
    }
});

另外,如果在onPageSelected事件处理程序中需要引用片段(fragment)的话,你需要自己做。我使用一个抽象基类来实现我的FragmentStatePagerAdapter,在其中重写了实例化和销毁方法,并从SparseArray中添加/删除片段。


6
发布可运行代码正是我所需要的秘密武器,确保在触发 onPageSelected 调用之前 viewPager 的视图已经被实例化。太好了! - RobP
完美。我终于找到了这个答案。 - flosk8
运行得非常顺畅。 - Moeen Kashisaz

35

好的,所以我还没有弄清楚这是一个 bug 还是一个功能。但我想分享一下如何解决这个问题的可能方法。

在 Activity 中编写您希望执行的功能,并在 onPageSelected 方法中调用此方法。

mPager.setOnPageChangeListener(new OnPageChangeListener() {
    @Override
    public void onPageSelected(int index) {
        myOnPageSelectedLogic(index);
    }
    ...
}

在调用之后立即执行

setCurrentItem(index);

在Activity中,添加以下if语句

if(index == 0) {
    myOnPageSelectedLogic(0);
}

虽然不是很漂亮,但我希望能对某些人有所帮助 :)


如果我想在片段本身上监听页面更改,我该怎么做? - Dr.jacky
2
实际上这是一个糟糕的例子,因为OnPageChangeListener在一些异步工作之后被调用,在许多情况下,使用这种“技巧”将无法正常工作。 - Defuera
@Defuera,我不明白你的关注点。你能详细说明一下吗?或者也许你可以提交一个更好的解决方案? - jpihl
3
更好的解决方案是在 view.post(new Runnable(){ setCurrentItem(index); } ); 中调用 setCurrentItem(index); 这段代码可以保证 OnPageChangeListener 被调用,至少在我的情况下是这样的。 - Defuera
@jpihl,我的OnPageSelectedLogic应该是什么? - Kala J
这是针对您的应用程序特定的。这个功能最初是想放在OnPageSelected回调中的。 - jpihl

27

实际上,我认为这是一个BUG。我已经检查了ViewPager的源代码,并发现唯一触发onPageSelected的方法:

Actually I think this is a BUG. I've check the source code of ViewPager and found the only trigger of onPageSelected:
if (dispatchSelected && mOnPageChangeListener != null) {
            mOnPageChangeListener.onPageSelected(item);
        }

上面的代码确定了变量dispatchSelected

final boolean dispatchSelected = mCurItem != item; 
//item is what you passed to setCurrentItem(item)

而且,变量 mCurItem 被定义并初始化为:

private int mCurItem; // Index of currently displayed page.

所以mCurItem默认为0。当调用setCurrentItem(0)时,dispatchSelected将为false,因此不会分派onPageSelected()。

现在我们应该理解为什么调用setCurrentItem(0)是一个问题,而调用setCurrentItem(1、2、3等)则不是。

如何解决这个问题:

  1. 将ViewPager的代码复制到您自己的项目中并进行修复:

    from 
    final boolean dispatchSelected = mCurItem != item; //the old line
    to
    final boolean dispatchSelected = mCurItem != item || mFirstLayout; //the new line
    

    那么使用您自己的ViewPager。

  2. 由于您有意调用了setCurrentItem(0)并且有一个OnPageChangedListener的引用,请自行调度onPageSelected()。

  3. 听楼下怎么说...


这里是源代码的链接:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/view/ViewPager.java - Nicramus
有没有一种方法可以在不复制代码的情况下完成这个操作?有没有在子类中修复它的方法? - mtmurdock
1
PagerAdapter.DataSetObserver无法解析为类型? - CoDe
2
很好的发现。你已经将此报告为问题了吗? - dumbfingers

3
以下解决方案对我来说似乎有效,即当viewpager首次加载时,我会在位置0处得到一个回调,并且对于所有后续的选择(无论是用户滚动还是setCurrentItem(x)调用),都会得到一个回调。 我没有观察到任何不良行为。
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

                if (positionOffsetPixels == 0) {
                 //Do something on selected page at position
                }

            }

            @Override
            public void onPageSelected(int position) {}

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

0
最简单的解决方案将是使用函数,例如:
    viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageScrollStateChanged(int state) {}

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}

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

    // call first time
    onPage(0); 

    private void onPage(int position) {
        //...
    }

0
您可以扩展ViewPager并在位置从0更改为0时自行调用监听器。
public class BetterViewPager extends ViewPager {

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

        // we some the listner
        protected OnPageChangeListener listener;

        @Override
        public void setOnPageChangeListener(OnPageChangeListener listener) {
            super.setOnPageChangeListener(listener);
            this.listener = listener;
        }

        @Override
        public void setCurrentItem(int item) {
            // when you pass set current item to 0,
            // the listener won't be called so we call it on our own
            boolean invokeMeLater = false;

            if(super.getCurrentItem() == 0 && item == 0)
                invokeMeLater = true;

            super.setCurrentItem(item);

            if(invokeMeLater && listener != null)
                listener.onPageSelected(0);
        }

}

这非常接近答案,但子片段尚未附加,因此如果您尝试调用getParentFragment或getActivity,它将失败。是否有其他地方可以做到这一点,以确保片段已附加? - mtmurdock
谁在这个答案中谈论活动和片段? - Marian Paździoch

0
如果ViewPager在一个Fragment中,在onViewCreated方法中调用mPager.setOnPageChangeListener(this)。

0

onPageSelected 在第一页被打开时不会被触发。对于这种情况,我手动执行所有必要的操作。此外,我认为调用 onPageSelected(0) 应该也可以。


1
好的,它是针对首次打开的页面触发的,也就是说如果这个页面不是第一个打开的页面。我知道这听起来很奇怪,但这就是我提出问题的原因。 - jpihl
2
触发 'onPageSelected()' 可能会导致 NullPointer! - paulgavrikov

0
public class PageChangeListener implements ViewPager.OnPageChangeListener {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE && getCurrentItem() == 0) {
         //this indicate viewpager finish scroll and page at position 0 is selected.
        }

-5

如果您不关心屏幕动画,请尝试

pager.setCurrentItem(1);
pager.setCurrentItem(0);

1
如果你的分页器只有一项呢? - RobP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接