ViewPager
とともにFragmentStatePagerAdapter
を使用して、いくつかのフラグメント間のナビゲーションを許可しています。
A
、B
、C
の3つのフラグメントがあるとします。 ViewPagerは最初にフラグメントAを表示し、右から左にスワイプしてフラグメントBに移動し、再度スワイプしてフラグメントCに移動できます。これにより、次のナビゲーションパスが許可されます:A <--> B <--> C
。
私が望むのは、フラグメントAを左から右にスワイプし、ViewPagerにフラグメントCを表示させることです。つまり、循環キューとして動作し、... --> C <--> A <--> B <--> C <--> A <-- ...
フラグメントが他の位置に複製されないようにします(つまり、3つ以上のインスタンスで終わる)。
ViewPagerでこのラッピング機能は可能ですか?
擬似無限のページング動作を可能にするViewPager/PagerAdapterを実装しました。実際のカウントとして非常に大きな数を指定することで機能しますが、データセット/ページセットの実際の範囲にマップします。また、「最初の」ページからすぐに左にスクロールできるように、先頭を大きな数だけオフセットします。
1,000,000ページに達するとうまく機能しません(スクロールするとグラフィカルな不具合が表示されます)が、これは通常、実際の使用例ではありません。カウントを時々低い値にリセットすることでこれを修正できましたが、今のところはそのままにしておきます。
InfinitePagerAdapter
は既存のViewPagerをラップするため、使用方法は非常に透過的です。 InfiniteViewPager
は、潜在的に何度も左右にスクロールできるようにするために少し作業を行います。
これは偽ページのないソリューションであり、魅力のように機能します。
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));
ちょうど今、この質問を見て解決策を見つけました。 ソリューションリンク
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 を参照してください。
それが役に立てば幸い。
これを行う別の方法は、ビューページャーの最初の要素の偽のコピーをアダプターの最後に追加し、最後の要素の偽のコピーを先頭に追加することです。ビューページャーで最初/最後の要素を表示しているときに、アニメーション化せずにページを変更します。
したがって、基本的に、ビューページャーの最初/最後の要素は偽物であり、正しいアニメーションを生成するためだけに存在し、終了/開始に切り替えます。例:
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);
}
}
}
これは、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
}
}
私はすべての提案、ソリューション、ライブラリなどを試してきましたが、それらは純粋な循環ではなく、ほとんどの場合、3ページしかサポートしていません。
したがって、新しい_ViewPager2
_を使用して循環ViewPager
の例を実装し、新しいViewPager
はRecyclerView
およびViewHolder
sを使用してビューのリサイクルと期待どおりに動作します!
TLDR:[〜#〜] github [〜#〜]
この例では、_ViewPager2
_とFragmentPagerAdapter
を使用して3ページ以上の循環ナビゲーションをサポートする単一のアクティビティアプリを構築します。
ライブラリのアルファバージョン_androidx.viewpager2:viewpager2
_を使用していますが、バージョン_1.0.0-alpha06
_は、GoogleがAPIをフリーズしてベータ版に移行する前に計画されている最後のバージョンです。
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)
}
_
これを実現する簡単な方法。配列にページがある場合、この配列を真ん中の別の配列に追加するだけです。
たとえば、15個の要素を持つ配列があります。中央の400要素配列(200の位置)に配置します
次に、配列を左右に埋めて、移動できるようにします。
サンプル画像:
見た目は?
コードに話させてください:)
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;
}
うまくいくと思う解決策があります。テストされていませんが、ここにあります:
スーパークラスとしての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));
}
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
}
});
}
}
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);
}
}
遅れてすみませんが、私は答えを見ました。
アダプターのメソッドget countで、次の手順を実行します。
@Override
public int getCount() {
return myList.size() % Integer.MAX_VALUE;
}
うまくいきます!
初期化コードのどこかに実際の最初の要素を選択します。
mViewPager.setCurrentItem(1);
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);
}
}
このアプローチ に基づいて
真のループ、ページャータイトルストリップ、真のリフレッシュスクロールなし
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;
}
}
});