ViewPager作为循环队列/包装

时间:2011-09-25 14:42:19

标签: android android-fragments android-viewpager

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

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

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

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

使用ViewPager可以实现此包装功能吗?

13 个答案:

答案 0 :(得分:48)

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

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

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

https://github.com/antonyt/InfiniteViewPager

答案 1 :(得分:16)

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

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 on on OnChangeListener即可:( ** 现已弃用 **查看编辑备注)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

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

android:overScrollMode="never"

我们改进了上述解决方案并在github上创建了一个小型库。请随意查看:)

编辑:: 根据@Bhavana评论,只需使用addOnPageChangeListener而不是setOnPageChangeListener,因为不推荐使用以后版本。

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));

答案 2 :(得分:7)

刚才看到这个问题并找到了解决方案。 Solution Link

对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函数here

希望它有所帮助。

答案 3 :(得分:6)

这几乎完成了antonyt想要的东西,它不会让你离开A - &gt;但是,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
    }
}

答案 4 :(得分:6)

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

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

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

}

答案 5 :(得分:3)

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

因此,我使用新的function getRedirect(pageId,title){ var redirectform = CardService.newAction() .setFunctionName('redirectCallback') .setParameters({page:pageId.toString()}); var redirectButton = CardService.newTextButton() .setText(title) .setOnClickAction(redirectform); return redirectButton; } function redirectCallback(e){ var pageId = parseInt(e.parameters.page); var newCard,redirectBtn; if(pageId == 1){ redirectBtn = getRedirect(2,"Next"); newCard = CardService.newCardBuilder() .setHeader(CardService.newCardHeader() .setTitle("Page2")) .addSection(CardService.newCardSection() .addWidget(CardService.newButtonSet() .addButton(redirectBtn))).build() } var navigate = CardService.newNavigation().pushCard(newCard); return CardService.newActionResponseBuilder() .setNavigation(navigate).build() } function buildAddOn() { var header = getHeaderSection(); var body = getBody(); var submitForm = CardService.newAction() .setFunctionName('openLinkCallback'); var submitButton = CardService.newTextButton() .setText('Open Google') .setOnClickAction(submitForm); var redirectBtn = getRedirectBtn(1,"Next"); var mainCard = CardService .newCardBuilder() .addSection(CardService.newCardSection() .addWidget(CardService.newButtonSet().addButton(submitButton))) .addSection(CardService.newCardSection() .addWidget(CardService.newButtonSet().addButton(redirectBtn))) .build(); return mainCard; } 实现了一个循环ViewPager示例,新的ViewPager2使用了ViewPagerRecyclerView来处理视图回收和按预期工作!

TLDR: GITHUB

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

我使用的是库FragmentPagerAdapter的Alpha版本,但版本androidx.viewpager2:viewpager2是Google冻结API并转为Beta之前计划的最后一个版本。

enter image description here

1。将1.0.0-alpha06库添加到build.gradle

中的依赖项中
ViewPager2

2。将dependencies { implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06' } 视图添加到您的项目中:

ViewPager2

3。创建<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" /> 适配器:

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

getItemCount()返回基于huuuuge号的中心页面。 此方法用于获取要在getCenterPage()中设置的初始页面的位置,在这种情况下,用户需要滑动约1073741823的时间才能到达ViewPager2的结尾。

ViewPager2

HomeScreens是带有页面信息的枚举。

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
}

4。将适配器设置为ViewPager

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

答案 6 :(得分:1)

简单的想法如何实现这一目标。当你的数组有页面时,只需将这个数组完全添加到中间的另一个数组中。

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

接下来只需向左右填充数组,这样就可以移动了。

示例图片:

enter image description here

看起来如何?

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

答案 7 :(得分: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; }
};

循环寻呼机适配器实施。

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

答案 8 :(得分:0)

对不起延迟,但我已经看到了答案,我无法抓住,也得回答:)。

在适配器上,按方法获取计数,按以下步骤操作:

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

那会有效!

答案 9 :(得分:0)

  1. 为物品设置一些较大的值,比如500000。
  2. 从适配器返回此值*实际项目数。
  3. 在getItem(int i)上将i调整为 - &gt; 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);
    
        }
    
    }
    

答案 10 :(得分:0)

  1. 在列表的开头添加最后元素的假副本。
  2. 在最后添加第一个元素的假副本。
  3. 在init代码中选择真正的First Element:

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

答案 11 :(得分:0)

基于this approach

true loop,pager title strip,true no refresh scrolling

<LinearLayout style="@style/summaryField">

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1.60">

                    <TextView
                        android:id="@+id/tv_lname_summary"                           
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout_marginLeft="10dp"
                        android:gravity="center_vertical" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="2.40">

                    <TextView
                        android:id="@+id/tv_getlname_summary"                            
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout_weight="3"
                        android:background="@color/userDataColor"
                        android:gravity="center_vertical" />

                </LinearLayout>

            </LinearLayout>

查看页面适配器:

 //Advert Summary Screen
    <style name="summaryField">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:orientation">horizontal</item>
        <item name="android:weightSum">4</item>
    </style>

使用:

<div>${data}</div>

答案 12 :(得分: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
            }
        });
    }
}