循环使用FragmentPagerAdapter的ViewPager

7
我想实现一个使用Fragments的ViewPager,在循环运动中可以进行滑动,例如页面(A<-->B<-->C<-->A)。 我阅读了一些关于如何做到这一点的帖子,例如返回多少元素的虚假计数,并将位置设置在中间。 如何创建循环ViewPager? 所有这些似乎都是基于PagerAdapter的。当我尝试类似的事情时,同时扩展FragmentPagerAdapter,只有两个Fragments时,我返回一个fakeCount页面就会得到异常,当我通过我的Fragments滑动时。 异常:java.lang.IllegalStateException:无法更改片段标记。
我认为这是因为FragmentManager认为我在位置2,但位置2指向位置0的Fragment。有人知道我如何避免这种情况吗?我想我应该尝试扩展FragmentManager。任何关于此的示例或帮助将不胜感激。

我已经为此实施了一个解决方案。我几乎完成了测试,等我完全结束后我会在这里回答。 - Fernando Camargo
@FernandoCamargo 你解决了吗? - Rahul Baradia
是的。非常抱歉,我忘记在这里回答了。现在我会发布答案。 - Fernando Camargo
3个回答

3

虽然有点晚了,但这是我操作的方法:

我需要在3个片段之间进行循环滑动,所以我创建了这3个片段和另外两个虚拟片段来帮助我实现页面循环:

public static class FirstViewFragment extends Fragment {
    // Empty Constructor
    public FirstViewFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_landing_1, container, false);
    }
}

public static class SecondViewFragment extends Fragment {
    // Empty Constructor
    public SecondViewFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_landing_2, container, false);
    }
}

public static class ThirdViewFragment extends Fragment {
    // Empty Constructor
    public ThirdViewFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_landing_3, container, false);
    }
}

还有两个虚拟片段,使我能够从第一个向左滑动,从最后一个向右滑动。第一个虚拟膨胀与最后一个实际相同的布局,最后一个虚拟膨胀与第一个实际相同的布局:

public static class StartVirtualFragment extends Fragment {
    public StartVirtualFragment() {}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_landing_3, container, false);
    }
}

public static class EndVirtualFragment extends Fragment {
    public EndVirtualFragment() {}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_landing_1, container, false);
    }
}

我的适配器:

private class ViewPagerAdapter extends FragmentPagerAdapter {

    public ViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        switch (i) {
            case 0:
                return new StartVirtualFragment();
            case 1:
                if (firstViewFragment == null) {
                    firstViewFragment = new FirstViewFragment();
                }
                return firstViewFragment;
            case 2:
                if (secondViewFragment == null) {
                    secondViewFragment = new SecondViewFragment();
                }
                return secondViewFragment;
            case 3:
                if (thirdViewFragment == null) {
                    thirdViewFragment = new ThirdViewFragment();
                }
                return thirdViewFragment;
            case 4:
                return new EndVirtualFragment();
        }
        return null;
    }

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

我使用 onPageScrollStateChanged 监听器来设置正确的页面并实现循环:

viewPager.setOnPageChangeListener(new 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_DRAGGING) {
                int pageCount = viewPager.getChildCount();
                int currentItem = viewPager.getCurrentItem();
                if (currentItem == 0) {
                    viewPager.setCurrentItem(pageCount - 2, false);
                } else if (currentItem == pageCount - 1) {
                    viewPager.setCurrentItem(1, false);
                }
            }
        }
    });

最终结果是:
viewPager.setCurrentItem(1);

希望我能帮上忙。


1

我在 GitHub 上有一个项目,其中包含一些我创建的小部件。这是它的链接:

https://github.com/CyberEagle/AndroidWidgets

以下是与CircularViewPager一起使用的适配器包: https://github.com/CyberEagle/AndroidWidgets/tree/master/src/main/java/br/com/cybereagle/androidwidgets/adapter 首先,在您的布局中将ViewPager替换为CircularViewPager。这里是CircularViewPager: https://github.com/CyberEagle/AndroidWidgets/blob/master/src/main/java/br/com/cybereagle/androidwidgets/view/CircularViewPager.java 这个ViewPager需要一个WrapperCircularPagerAdapter,而不是PagerAdapter。这个包装器用于欺骗ViewPager,让它认为在ViewPager中有很多项,但实际上重复你的项目以产生循环效果。因此,您将实现CircularFragmentPagerAdapter、CircularFragmentStatePagerAdapter或CircularPagerAdapter,而不是实现PagerAdapter、FragmentPagerAdapter或FragmentStatePagerAdapter。然后,您将使用WrapperCircularPagerAdapter包装适配器,并将包装器设置在CircularViewPager中,而不是适配器中。此外,在通知数据集更改时,您将在包装器中调用notifyDatasetChanged()。
当实现其中一个循环适配器时,您会注意到,您将不得不实现instantiateVirtualItem而不是instantiateItem。对于片段的pager适配器,您将实现getVirtualItem而不是getItem。这是因为我创建了虚拟项目的概念。
为了使其清晰明了,想象一下一个具有4个项目的视图页,每个项目代表一首音乐。当你向左走到底时,你会看到第一个左边的第4个项目。实际上,它是一个全新的项目,但它与代表第4个音乐的虚拟项目相连。
另一个例子:想象现在只有一首音乐。您将在左侧和右侧看到相同的音乐。每次同时只有一个虚拟项目,但是每次会显示3个项目。
正如所解释的那样,Wrapper欺骗ViewPager,使其认为有很多项。为了让用户更难达到ViewPager的一端(无论如何都需要很长时间),每当数据集发生变化时,ViewPager都会转到相同的虚拟项目,但是会转到靠近中间的实际项目之一。
还有一件重要的事情是CircularViewPager具有setCurrentVirtualItem方法。该方法计算最接近所需虚拟项目的实际项目,然后使用setCurrentItem进行设置。您还可以选择使用getCurrentVirtualItem,它将返回当前虚拟项的索引。请注意,如果使用getCurrentItem,则会得到大的索引值。
好的,这就是全部内容。对于该项目缺乏文档表示抱歉。我计划尽快对其进行文档化,并计划删除包装器的需求。请随意复制代码(遵守Apache 2.0许可证),分叉或甚至为其做出贡献。

我已经按照您所说的实现了View Pager。我正在使用您提到的CircularViewPager、WrapperCircularPagerAdapter和CircularFragmentPagerAdapter类。 但仍然出现异常:Exception: java.lang.IllegalStateException: Can't change tag of fragment。有什么建议吗? - MiaN KhaLiD
如果我添加4个片段,那么我的代码就可以正常工作。但是我的要求是只使用2个片段,这会导致上述异常。 - MiaN KhaLiD
@MiaNKhaLiD,很奇怪。即使只有一个片段,它也对我有效。你能展示一下你是如何使用它的吗? - Fernando Camargo
实际上,我也在尝试重用片段,所以我在你的CircularPagerAdapter类中添加了一些代码来实现这个功能。 代码链接:示例Dropbox链接 目前,如果你运行这段代码,它将有4个片段/页面,并且可以正常运行。但是,如果你将它们更改为2个,应用程序会在启动时崩溃。 - MiaN KhaLiD
@MiaNKhaLiD,我认为您不能重复使用碎片实例。最少,ViewPager将尝试拥有3个碎片:可见的、左侧的和右侧的。如果您有2个实例,您就不能拥有3个碎片。所以要创建多个相同类型的碎片实例。重复使用碎片是不可能的。 - Fernando Camargo

-3
**If you want to make 3 views visible at same time and make it circular** 

    public abstract class CircularPagerAdapter extends PagerAdapter{
        private int count;
        int[] pagePositionArray;
        public static final int EXTRA_ITEM_EACH_SIDE = 2;
        private ViewPager.OnPageChangeListener pageChangeListener;
        private ViewPager viewPager;

        public CircularPagerAdapter(final ViewPager pager, int originalCount ) {
            super();
            this.viewPager = pager;
            count = originalCount + 2*EXTRA_ITEM_EACH_SIDE;
            pager.setOffscreenPageLimit(count-2);
            pagePositionArray = new int[count];
            for (int i = 0; i < originalCount; i++) {
                pagePositionArray[i + EXTRA_ITEM_EACH_SIDE] = i;
            }
            pagePositionArray[0] = originalCount - 2;
            pagePositionArray[1] = originalCount -1;
            pagePositionArray[count - 2] = 0;
            pagePositionArray[count - 1] = 1;

            pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

                public void onPageSelected(final int position) {
                    if(pageChangeListener != null)
                    {
                        pageChangeListener.onPageSelected(pagePositionArray[position]);
                    }

                    pager.post(new Runnable() {
                        @Override
                        public void run() {
                            if (position == 1){
                                pager.setCurrentItem(count-3,false);
                            } else if (position == count-2){
                                pager.setCurrentItem(2,false);
                            }
                        }
                    });
                }

                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    if(pageChangeListener != null)
                    {
                        pageChangeListener.onPageScrolled(pagePositionArray[position],positionOffset,positionOffsetPixels);
                    }
                }

                public void onPageScrollStateChanged(int state) {
                    if(pageChangeListener != null)
                    {
                        pageChangeListener.onPageScrollStateChanged(state);
                    }
                }
            });
        }


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

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

        public abstract Object customInstantiateItem(ViewGroup container, int position);

        public void setPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener)
        {
            this.pageChangeListener = pageChangeListener;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            int pageId = pagePositionArray[position];
            return customInstantiateItem(container,pageId);
        }

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

        public void setFirstItem()
        {
            viewPager.setCurrentItem(EXTRA_ITEM_EACH_SIDE - 1);
        }
    }

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