web-dev-qa-db-ja.com

循環キュー/ラッピングとしてのViewPager

ViewPagerとともにFragmentStatePagerAdapterを使用して、いくつかのフラグメント間のナビゲーションを許可しています。

ABCの3つのフラグメントがあるとします。 ViewPagerは最初にフラグメントAを表示し、右から左にスワイプしてフラグメントBに移動し、再度スワイプしてフラグメントCに移動できます。これにより、次のナビゲーションパスが許可されます:A <--> B <--> C

私が望むのは、フラグメントAを左から右にスワイプし、ViewPagerにフラグメントCを表示させることです。つまり、循環キューとして動作し、... --> C <--> A <--> B <--> C <--> A <-- ...

フラグメントが他の位置に複製されないようにします(つまり、3つ以上のインスタンスで終わる)。

ViewPagerでこのラッピング機能は可能ですか?

64
antonyt

擬似無限のページング動作を可能にするViewPager/PagerAdapterを実装しました。実際のカウントとして非常に大きな数を指定することで機能しますが、データセット/ページセットの実際の範囲にマップします。また、「最初の」ページからすぐに左にスクロールできるように、先頭を大きな数だけオフセットします。

1,000,000ページに達するとうまく機能しません(スクロールするとグラフィカルな不具合が表示されます)が、これは通常、実際の使用例ではありません。カウントを時々低い値にリセットすることでこれを修正できましたが、今のところはそのままにしておきます。

InfinitePagerAdapterは既存のViewPagerをラップするため、使用方法は非常に透過的です。 InfiniteViewPagerは、潜在的に何度も左右にスクロールできるようにするために少し作業を行います。

https://github.com/antonyt/InfiniteViewPager

48
antonyt

これは偽ページのないソリューションであり、魅力のように機能します。

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

それをonPageChangeListenerとしてViewPagerに設定するだけです。(**deprecated now**編集メモを確認してください)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

ViewPagerの「終わり」でこの青い輝きを避けるために、ViewPagerが配置されているxmlに次の行を適用する必要があります。

Android:overScrollMode="never"

上記のソリューションを改善し、 github に小さなライブラリを作成しました。気軽にチェックしてください:)

Edit ::@Bhavanaコメントに従って、後で廃止されるため、setOnPageChangeListenerの代わりにaddOnPageChangeListenerを使用してください。

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));
16
King of Masses

ちょうど今、この質問を見て解決策を見つけました。 ソリューションリンク

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

                }

            }

        }

state変数が既存のタブにある場合、その値は1です。

before最初のタブまたはafterに移動しようとすると0になります最後のタブ。したがって、コードに示されているように、以前の状態と現在の状態を比較すれば完了です。

OnPageScrollStateChanged関数 here を参照してください。

それが役に立てば幸い。

7
Bhasker

これを行う別の方法は、ビューページャーの最初の要素の偽のコピーをアダプターの最後に追加し、最後の要素の偽のコピーを先頭に追加することです。ビューページャーで最初/最後の要素を表示しているときに、アニメーション化せずにページを変更します。

したがって、基本的に、ビューページャーの最初/最後の要素は偽物であり、正しいアニメーションを生成するためだけに存在し、終了/開始に切り替えます。例:

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

}
7
Graydyn Young

これは、antonytが望んでいることをほぼ達成しますが、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
    }
}
6
NPike

私はすべての提案、ソリューション、ライブラリなどを試してきましたが、それらは純粋な循環ではなく、ほとんどの場合、3ページしかサポートしていません。

したがって、新しい_ViewPager2_を使用して循環ViewPagerの例を実装し、新しいViewPagerRecyclerViewおよびViewHoldersを使用してビューのリサイクルと期待どおりに動作します!

TLDR:[〜#〜] github [〜#〜]

この例では、_ViewPager2_とFragmentPagerAdapterを使用して3ページ以上の循環ナビゲーションをサポートする単一のアクティビティアプリを構築します。

ライブラリのアルファバージョン_androidx.viewpager2:viewpager2_を使用していますが、バージョン_1.0.0-alpha06_は、GoogleがAPIをフリーズしてベータ版に移行する前に計画されている最後のバージョンです。

enter image description here

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_に設定する初期ページの位置を取得するために使用されます。この場合、ユーザーは、_ViewPager2_の最後に到達するまでに約1073741823回スワイプする必要があります。

_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)
}
_
5
extmkv

これを実現する簡単な方法。配列にページがある場合、この配列を真ん中の別の配列に追加するだけです。

たとえば、15個の要素を持つ配列があります。中央の400要素配列(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;
}
1
Dawid Drozd

うまくいくと思う解決策があります。テストされていませんが、ここにあります:

スーパークラスとしての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));
    }
0
user2646772

https://github.com/pozitiffcat/cyclicview からシンプルなビューを使用できます
機能の1つは次のとおりです。

最初と最後のビューを複製せず、代わりにビットマップバリアントを使用します

例:

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
            }
        });
    }
}
  1. アイテムに設定すると、いくつかの大きな値、たとえば500000がカウントされます。
  2. アダプタからこの値*実際のアイテムの数を返します。
  3. GetItem(int i)で、iを-> i = i%実際のアイテムの数に調整します。
  4. OnCreate(Bundle savedInstanceState)On現在のアイテムをページ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);
    
        }
    
    }
    
0
Kvant

遅れてすみませんが、私は答えを見ました。

アダプターのメソッドget countで、次の手順を実行します。

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

うまくいきます!

0
JasJar
  1. リストの最初に最後の要素の偽のコピーを追加します。
  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);
            }
        }
    
0
Alyk

このアプローチ に基づいて

真のループ、ページャータイトルストリップ、真のリフレッシュスクロールなし

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