64

我正在使用ViewPagerwithFragmentStatePagerAdapter来允许在某些片段之间导航。

假设我有三个片段ABC。ViewPager 最初显示 Fragment A,并允许您通过从右向左滑动导航到 Fragment B,然后通过再次滑动导航到 Fragment C。这允许以下导航路径:A <--> B <--> C.

我想要的是能够在 Fragment A 上从左向右滑动并让 ViewPager 显示 Fragment C,即让它表现为一个循环队列并允许... --> C <--> A <--> B <--> C <--> A <-- ...

我不希望片段在其他位置重复(即以三个以上的实例结束)。

ViewPager 可以实现这种包装功能吗?

4

13 回答 13

49

我已经实现了一个 ViewPager/PagerAdapter,它可以允许伪无限分页行为。它通过指定一个非常大的数字作为实际计数来工作,但将它们映射到数据集/页面集的实际范围。它还会将开头偏移很大,以便您可以立即从“第一”页向左滚动。

一旦您到达第 1,000,000 页,它就不能很好地工作(滚动时您会看到图形故障),但这通常不是真实世界的用例。我可以通过偶尔将计数重置为较低的数字来解决此问题,但我将暂时保留它的状态。

包装了现有的InfinitePagerAdapterViewPager,因此使用非常透明。确实做了InfiniteViewPager一些工作以确保您可以多次向左和向右滚动。

https://github.com/antonyt/InfiniteViewPager

于 2012-03-11T16:51:45.740 回答
19

这是一个没有虚假页面的解决方案,就像一个魅力:

public class CircularViewPagerHandler implements ViewPager.OnPageChangeListener {
    private ViewPager   mViewPager;
    private int         mCurrentPosition;
    private int         mScrollState;

    public CircularViewPagerHandler(final ViewPager viewPager) {
        mViewPager = viewPager;
    }

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

    @Override
    public void onPageScrollStateChanged(final int state) {
        handleScrollState(state);
        mScrollState = state;
    }

    private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            setNextItemIfNeeded();
        }
    }

    private void setNextItemIfNeeded() {
        if (!isScrollStateSettling()) {
            handleSetNextItem();
        }
    }

    private boolean isScrollStateSettling() {
        return mScrollState == ViewPager.SCROLL_STATE_SETTLING;
    }

    private void handleSetNextItem() {
        final int lastPosition = mViewPager.getAdapter().getCount() - 1;
        if(mCurrentPosition == 0) {
            mViewPager.setCurrentItem(lastPosition, false);
        } else if(mCurrentPosition == lastPosition) {
            mViewPager.setCurrentItem(0, false);
        }
    }

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

您只需将其设置为您的 ViewPager 作为 onPageChangeListener 就可以了:( **现已弃用** 检查编辑说明)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

为避免在 ViewPager 的“末尾”出现这种蓝色光芒,您应该将此行应用于放置 ViewPager 的 xml:

android:overScrollMode="never"

我们改进了上面的解决方案,并在github上创建了一个小库。随意检查一下:)

编辑::根据@Bhavana 评论,只需使用 addOnPageChangeListener 而不是 setOnPageChangeListener,因为稍后已弃用。

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));
于 2015-06-08T09:26:56.227 回答
10

我一直在尝试所有的建议、解决方案、库等,但它们不是纯循环的,而且大多数时候只支持 3 页。

所以我ViewPager使用 new 实现了一个循环示例ViewPager2, newViewPager使用 aRecyclerViewViewHolders 来处理视图回收并按预期工作!

TLDR: Github

在此示例中,将构建一个具有3 个或更多页面之间ViewPager2FragmentPagerAdapter支持循环导航的单个活动应用程序。

我正在使用该库的 alpha 版本androidx.viewpager2:viewpager2,但该版本1.0.0-alpha06是谷歌冻结 API 并转移到 beta 之前计划的最后一个版本。

在此处输入图像描述

1.将ViewPager2库添加到你的build.gradle中的依赖项

dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06'
}

2. 将ViewPager2视图添加到您的项目中:

<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/vwpHome"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. 创建FragmentStateAdapter适配器:

getItemCount()需要返回一个 huuuuge 数字。(2147483647)

getCenterPage()根据 huuuuge 编号返回中心页面。该方法用于获取要设置的初始页面的位置ViewPager2,在这种情况下,用户需要滑动〜1073741823次才能到达末尾ViewPager2

class CircularPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) {

    override fun getItemCount() = Integer.MAX_VALUE

    /**
     * Create the fragment based on the position
     */
    override fun createFragment(position: Int) = HomePagerScreens.values()[position % HomePagerScreens.values().size].fragment.java.newInstance()

    /**
     * Returns the same id for the same Fragment.
     */
    override fun getItemId(position: Int): Long = (position % HomePagerScreens.values().size).toLong()

    fun getCenterPage(position: Int = 0) = Integer.MAX_VALUE / 2 + position
}

HomeScreens 是一个带有页面信息的 ENUM。

enum class HomePagerScreens(@StringRes val title: Int,
                            val fragment: KClass<out Fragment>) {

    HOME_1(R.string.home_1, FragmentHome::class),
    HOME_2(R.string.home_2, FragmentHome::class),
    HOME_3(R.string.home_3, FragmentHome::class)
}

4.将适配器设置为ViewPager

val circularAdapter = CircularPagerAdapter(supportFragmentManager, lifecycle)
vwpHome.apply {
    adapter = circularAdapter
    setCurrentItem(circularAdapter.getCenterPage(), false)
}
于 2019-07-12T09:58:31.857 回答
9

刚刚看到这个问题,找到了解决办法。解决方案链接

对 onPageScrollStateChanged 函数进行以下更改。假设您有4 个选项卡。

private int previousState, currentState;

public void onPageScrollStateChanged(int state) {              

            int currentPage = viewPager.getCurrentItem();       //ViewPager Type
            if (currentPage == 3 || currentPage == 0){
                previousState = currentState;
                currentState = state;
                if (previousState == 1 && currentState == 0){

                    viewPager.setCurrentItem(currentPage == 0 ? 3 : 0);

                }

            }

        }

如果状态变量在现有选项卡中,则其值为 1。

一旦您尝试在第一个选项卡之前或最后一个选项卡之后导航,它就会变为 0。因此,比较先前和当前状态,如代码所示,您就完成了。

请参阅此处的onPageScrollStateChanged 函数。

希望能帮助到你。

于 2014-01-27T10:14:41.670 回答
9

另一种方法是将视图寻呼机中第一个元素的假副本添加到适配器的末尾,并将最后一个元素的假副本添加到开头。然后,当您在视图分页器中查看第一个/最后一个元素时,无需动画即可更改页面。

所以基本上,您的视图寻呼机中的第一个/最后一个元素是假的,只是为了产生正确的动画,然后切换到结束/开始。例子:

list.add(list.get(0)); //add the first item again as the last, to create the wrap effect
list.add(0, list.get(list.size()-2)); //insert a copy of the last item as the new first item, so we can scroll backwards too

viewPager.setAdapter(list);
viewPager.setCurrentItem(1, false); //default to the second element (because the first is now a fake)

viewPager.setOnPageChangeListener(new OnPageChangeListener() {
private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) { //this is triggered when the switch to a new page is complete
            final int lastPosition = viewPager.getAdapter().getCount() - 1;
            if (currentPosition == lastPosition) {
                viewPager.setCurrentItem(1, false); //false so we don't animate
            } else if (currentPosition == 0) {
                viewPager.setCurrentItem(lastPosition -1, false);
            }
        }

}
于 2014-08-29T19:09:23.050 回答
6

这几乎完成了安东尼想要的,但它不会让你从 A -> C 走。(没有经过大力测试,但似乎工作得很好)

public class MyAdapter extends PagerAdapter {
    private List<Object> mItems;
    private int mFakeCount = 0;

    public MyAdapter(Context context, List<Object> items) {
        mItems = items;
        mFakeCount = mItems.size()+1;
    }

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

    @Override
    public Object instantiateItem(View collection, int position) {
        // make the size larger, and change the position
        // to trick viewpager into paging forever
        if (position >= mItems.size()-1) {
            int newPosition = position%mItems.size();

            position = newPosition;
            mFakeCount++;
        }

        // do your layout inflating and what not here.
        // position will now refer to a correct index into your mItems list
        // even if the user has paged so many times that the view has wrapped
    }
}
于 2011-11-29T00:55:34.383 回答
2

简单的想法如何实现这一点。当您拥有带有页面的数组时,只需将此数组添加到恰好位于中间的另一个数组中。

例如,您有一个包含 15 个元素的数组。所以把它放在中间的400个元素数组中(200个位置)

接下来只需向左右填充数组,以便您可以四处移动。

示例图片:

在此处输入图像描述

它看起来如何?

https://youtu.be/7up36ylTzXk

让代码说话:)

https://gist.github.com/gelldur/051394d10f6f98e2c13d

public class CyclicPagesAdapter extends FragmentStatePagerAdapter {

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

    @Override
    public Fragment getItem(int position) {
        return _fragments.get(position).createInstance();
    }

    @Override
    public int getCount() {
        return _fragments.size();
    }

    public void addFragment(FragmentCreator creator) {
        _fragments.add(creator);
    }

    public interface FragmentCreator {
        Fragment createInstance();
    }

    public void clear() {
        _fragments.clear();
    }

    public void add(FragmentCreator fragmentCreator) {
        _fragments.add(fragmentCreator);
    }

    public void finishAdding() {
        ArrayList<FragmentCreator> arrayList = new ArrayList<>(Arrays.asList(new FragmentCreator[MAX_PAGES]));
        arrayList.addAll(MAX_PAGES / 2, _fragments);

        int mrPointer = _fragments.size() - 1;
        for (int i = MAX_PAGES / 2 - 1; i > -1; --i) {
            arrayList.set(i, _fragments.get(mrPointer));
            --mrPointer;
            if (mrPointer < 0) {
                mrPointer = _fragments.size() - 1;
            }
        }

        mrPointer = 0;
        for (int i = MAX_PAGES / 2 + _fragments.size(); i < arrayList.size(); ++i) {
            arrayList.set(i, _fragments.get(mrPointer));
            ++mrPointer;
            if (mrPointer >= _fragments.size()) {
                mrPointer = 0;
            }
        }
        _fragmentsRaw = _fragments;
        _fragments = arrayList;
    }

    public int getStartIndex() {
        return MAX_PAGES / 2;
    }

    public int getRealPagesCount() {
        return _fragmentsRaw.size();
    }

    public int getRealPagePosition(int index) {
        final FragmentCreator fragmentCreator = _fragments.get(index);
        for (int i = 0; i < _fragmentsRaw.size(); ++i) {
            if (fragmentCreator == _fragmentsRaw.get(i)) {
                return i;
            }
        }
        //Fail...
        return 0;
    }

    private static final int MAX_PAGES = 500;
    public ArrayList<FragmentCreator> _fragments = new ArrayList<>();
    public ArrayList<FragmentCreator> _fragmentsRaw;
}
于 2015-10-06T22:11:39.420 回答
0

我有一个我相信会奏效的解决方案。它没有经过测试,但在这里:

作为超类的 FragmentPagerAdapter 的包装器:

public abstract class AFlexibleFragmentPagerAdapter extends
        FragmentPagerAdapter
{
    public AFlexibleFragmentPagerAdapter(FragmentManager fm)
    {
        super(fm);
        // TODO Auto-generated constructor stub
    }

    public abstract int getRealCount();

    public abstract int getStartPosition();

    public abstract int transformPosition(int position);
};

然后标准的 FragmentPagerAdapter 实现子类化这个新的抽象类:

public class SomeFragmentPagerAdapter extends
        AFlexibleFragmentPagerAdapter
{

    private Collection<Fragment> someContainer;

    public SomeFragmentPagerAdapter(FragmentManager fm, Container<Fragment> fs)
    {
        super(fm);
        someContainer = fs;
    }

    @Override
    public int getRealCount() { return someContainer.size(); }

    @Override
    public int getStartPosition() { return 0; }

    @Override
    public int transformPosition(int position) { return position; }
};

以及 Cyclic Pager Adapter 实现。

public class SomeCyclicFragmentPagerAdapter extends
        SomeFragmentPagerAdapter 
{
    private int realCount = 0;
    private int dummyCount = 0;
    private static final int MULTI = 3;

    public SomeFragmentPagerAdapter(FragmentManager fm, ...)
    {
        super(fm);
        realCount = super.getCount();
        dummyCount *= MULTI;
    }

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

    @Override
    public int getRealCount() { return realCount; }

    @Override
    public int getStartPosition() { return realCount; }

    @Override
    public int transformPosition(int position)
    {
        if (position < realCount) position += realCount;
        else if (position >= (2 * realCount)) position -= realCount;

        return position;
    }
};

ViewPager 初始化

    this.mViewPager.setCurrentItem(this.mPagerAdapter.getStartPosition());

最后,回调实现:

    @Override
    public void onPageSelected(int position)
    {
        this.mTabHost.setCurrentTab(position % mPagerAdapter.getRealCount());
        this.mViewPager.setCurrentItem(this.mPagerAdapter
                .transformPosition(position));
    }

    @Override
    public void onTabChanged(String tabId)
    {
        int pos = this.mTabHost.getCurrentTab();

        this.mViewPager.setCurrentItem(this.mPagerAdapter
                .transformPosition(pos));
    }
于 2013-08-03T18:58:46.540 回答
0
  1. 在列表的开头添加 Last Element 的假副本。
  2. 在末尾添加第一个元素的假副本。
  3. 在初始化代码中的某处选择真正的第一个元素:

        mViewPager.setCurrentItem(1);
    
  4. 修改 setOnPageChangeListener:

        @Override
        public void onPageSelected(int state) {
            if (state == ITEMS_NUMBER + 1) {
                mViewPager.setCurrentItem(1, false);
            } else if (state == 0) {
                mViewPager.setCurrentItem(ITEMS_NUMBER, false);
            }
        }
    
于 2016-05-13T14:58:34.380 回答
0

您可以使用来自https://github.com/pozitiffcat/cyclicview的简单视图
功能之一是:

不要重复第一个和最后一个视图,而是使用位图变体

例子:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CyclicView cyclicView = (CyclicView) findViewById(R.id.cyclic_view);
        cyclicView.setAdapter(new CyclicAdapter() {
            @Override
            public int getItemsCount() {
                return 10;
            }

            @Override
            public View createView(int position) {
                TextView textView = new TextView(MainActivity.this);
                textView.setText(String.format("TextView #%d", position + 1));
                return textView;
            }

            @Override
            public void removeView(int position, View view) {
                // Do nothing
            }
        });
    }
}
于 2016-10-13T14:18:28.860 回答
0
  1. 为项目设置一些较大的值,例如 500000。
  2. 从适配器返回此值 * 实际项目数。
  3. 在 getItem(int i) 上将 i 调整为 -> i = i % 实际项目数。
  4. onCreate(Bundle savedInstanceState) 为页面 500000/2 设置当前项目

    public class MainActivity extends FragmentActivity {
    
        private final static int ITEMS_MAX_VALUE = 500000;
        private int actualNumCount = 10;
        private ViewPager mPager = null;
    
        private class OptionPageAdapter extends FragmentStatePagerAdapter {
            public OptionPageAdapter(FragmentManager fm) {
                super(fm);
            }
    
            @Override
            public Fragment getItem(int i) {
                try {
                    i = i % OptionType.values().length;
                    OptionPage optionPage = new OptionPage(i);
                    optionPage.id = i;
                    return optionPage;
                } catch (Exception e) {
                    VayoLog.log(Level.WARNING, "Unable to create OptionFragment for id: " + i, e);
                }
                return null;
            }
    
            @Override
            public int getCount() {
                return ITEMS_MAX_VALUE * actualNumCount;
            }
        }
    
        public static class OptionPage extends Fragment {
            private int id = 0
            @Nullable
            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                View mainView = inflater.inflate(R.layout.main_page, container, false);
                return mainView;
            }
    
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            mPager = (ViewPager) findViewById(R.id.main_pager);
            mPager.setOffscreenPageLimit(3);
            mPager.setAdapter(new OptionPageAdapter(this.getSupportFragmentManager()));
            mPager.setCurrentItem(ITEMS_MAX_VALUE/2,false);
    
        }
    
    }
    
于 2015-12-15T15:15:41.090 回答
0

基于这种方法

真正的循环,寻呼机标题条,真正的无刷新滚动

private static final int ITEM_NUM = 5 ; // 5 for smooth pager title strip
private void addFragment(String title, String className) {
    if (fragments.size() < ITEM_NUM) {
        Bundle b = new Bundle();
        b.putInt("pos", fragments.size());
        fragments.add(Fragment.instantiate(this, className, b));

    }
    titles.add(title);
    itemList.add(className);
}

查看页面适配器:

public static class MyFragmentPageAdapter extends FragmentPagerAdapter {

    private HashMap<Long, Fragment> mItems = new HashMap<Long, Fragment>();

    public MyFragmentPageAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public long getItemId(int position) {
        int id = java.lang.System.identityHashCode(fragments.get(position));
        return id;
    }

    @Override
    public Fragment getItem(int position) {
        long id = getItemId(position);
        if (mItems.get(id) != null) {
            return mItems.get(id);
        }
        Fragment f = fragments.get(position);
        return f;
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        String t = titles.get(getItem(position).getArguments()
                .getInt("pos"));
        return t;
    }

    @Override
    public int getItemPosition(Object object) {
        for (int i = 0; i < ITEM_NUM; i++) {
            Fragment item = (Fragment) fragments.get(i);
            if (item.equals((Fragment) object)) {
                return i;
            }
        }
        return POSITION_NONE;
    }
}

使用:

    itemList = new ArrayList<String>();
    fragments = new ArrayList<Fragment>();
    titles = new ArrayList<String>();

    addFragment("Title1", Fragment1.class.getName());
    ...
    addFragment("TitleN", FragmentN.class.getName());

    mAdapter = new MyFragmentPageAdapter(getSupportFragmentManager());
    mViewPager = (ViewPager) findViewById(R.id...);

    mViewPager.setAdapter(mAdapter);
    mViewPager.setCurrentItem(2);
    currPage = 2;
    visibleFragmentPos = 2;

    mViewPager.setOnPageChangeListener(new OnPageChangeListener() {

        @Override
        public void onPageSelected(int curr) {
            dir = curr - currPage;
            currPage = curr;
        }

        private void shiftRight() {
            fragments.remove(0);
            int pos = visibleFragmentPos + 3;
            if (pos > itemList.size() - 1)
                pos -= itemList.size();
            if (++visibleFragmentPos > itemList.size() - 1)
                visibleFragmentPos = 0;
            insertItem(4, pos);

        }

        private void shiftLeft() {
            fragments.remove(4);
            int pos = visibleFragmentPos - 3;
            if (pos < 0)
                pos += itemList.size();
            if (--visibleFragmentPos < 0)
                visibleFragmentPos = itemList.size() - 1;
            insertItem(0, pos);
        }

        private void insertItem(int datasetPos, int listPos) {
            Bundle b = new Bundle();
            b.putInt("pos", listPos);
            fragments.add(datasetPos,
                    Fragment.instantiate(ctx, itemList.get(listPos), b));
        }

        @Override
        public void onPageScrollStateChanged(int state) {

            if (state == ViewPager.SCROLL_STATE_IDLE) {
                if (dir == 1) {
                    shiftRight();
                } else if (dir == -1) {
                    shiftLeft();

                }
                mAdapter.notifyDataSetChanged();
                currPage = 2;
            }
        }
    });
于 2016-07-04T08:54:29.817 回答
-1

抱歉耽搁了,但我已经看到了答案,我无法坚持,也必须回答:)。

在您的适配器上,在 get count 方法上,执行以下操作:

 @Override
    public int getCount() {
        return myList.size() % Integer.MAX_VALUE; 
  }

那可行!

于 2014-04-06T16:28:40.087 回答