Подтвердить что ты не робот

ViewPager как круговая очередь/упаковка

Я использую ViewPager с FragmentStatePagerAdapter, чтобы разрешить навигацию между некоторыми фрагментами.

Скажем, у меня есть три фрагмента: A, B и C. Сначала ViewPager показывает фрагмент A и позволяет перейти к фрагменту B, прокручивая справа налево, а затем в фрагмент C, снова прокручивая. Это позволяет использовать следующие пути навигации: A <--> B <--> C.

Что бы я хотел, это иметь возможность прокручивать слева направо на фрагменте A и показывать ViewPager Fragment C, т.е. вести себя как круговую очередь и разрешать ... --> C <--> A <--> B <--> C <--> A <-- ...

Я не хочу, чтобы фрагменты дублировались в других позициях (т.е. заканчивались более чем тремя экземплярами).

Возможно ли использование этой функции обтекания с помощью ViewPager?

4b9b3361

Ответ 1

Я реализовал ViewPager/PagerAdapter, который может допускать псевдо-бесконечное поведение подкачки. Он работает, указывая очень большое число как фактическое количество, но сопоставляет их с фактическим диапазоном набора данных/набора страниц. Он также смещает начало на большое количество, чтобы вы могли сразу прокручивать влево на первой странице.

Это не работает так хорошо, как только вы доберетесь до 1,000,000-й страницы (вы увидите графические сбои при прокрутке), но это, как правило, не используется в реальных условиях. Я мог бы исправить это, сбросив счетчик до более низкого числа раз в то время, но я оставлю его таким, как сейчас.

InfinitePagerAdapter обертывает существующий ViewPager, поэтому использование довольно прозрачно. InfiniteViewPager выполняет небольшую работу, чтобы убедиться, что вы можете прокручивать влево и вправо много раз.

https://github.com/antonyt/InfiniteViewPager

Ответ 2

Это решение без поддельных страниц и работает как шарм:

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, вы должны применить эту строку к вашему XML, где размещен ViewPager:

android:overScrollMode="never"

Мы улучшили решение выше и создали небольшую библиотеку на github. Не стесняйтесь проверить это :)

Edit :: В соответствии с комментарием @Bhavana, просто используйте addOnPageChangeListener вместо setOnPageChangeListener, так как позже это устарело.

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));

Ответ 3

Только что увидел этот вопрос и нашел решение. Ссылка на решение

Внесите следующие изменения в свою функцию 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 здесь.

Надеюсь, что это поможет.

Ответ 4

Это почти выполняет то, что хочет 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
    }
}

Ответ 5

Другой способ сделать это - добавить поддельную копию первого элемента в пейджере представления в конец вашего адаптера и поддельную копию последнего элемента в начало. Затем, когда вы просматриваете первый/последний элемент в представлении пейджера, измените страницы без анимации.

Итак, в первую очередь, первые/последние элементы в вашем плеере просмотра - это подделки, просто чтобы создать правильную анимацию, а затем перейти к концу/началу. Пример:

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

}

Ответ 6

Я пробовал все предложения, решения, библиотеки и т.д., Но они не являются чисто циклическими, и большую часть времени не поддерживают только 3 страницы.

Поэтому я реализовал круговой пример ViewPager используя новый ViewPager2, новый ViewPager использует RecyclerView и ViewHolder для обработки ViewHolder просмотра и работает как ожидалось!

TL;DR: 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, в этом случае пользователю необходимо провести по ˜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)
}

Ответ 7

Простая идея, как добиться этого. Когда у вас есть массив со страницами, просто добавьте этот массив в другой массив точно посередине.

Например, у вас есть массив из 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;
}

Ответ 8

У меня есть решение, которое, как я считаю, будет работать. Он не проверен, но вот он:

Обертка вокруг 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));
    }

Ответ 9

Извините за задержку, но я видел ответы, и я не смог удержаться, на это тоже ответил:).

На вашем адаптере, по методу get count, действуйте следующим образом:

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

Это сработает!

Ответ 10

  • Устанавливается для элементов, подсчитывающих некоторое большое значение, например 500000.
  • Возврат из адаптера этого значения * количество фактических элементов.
  • В getItem (int i) отредактируйте я - - я = i% количество фактических элементов.
  • В 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);
    
        }
    
    }
    

Ответ 11

  • добавьте поддельную копию последнего элемента в начале списка.
  • добавить поддельную копию первого элемента в конце.
  • выберите реальный первый элемент где-нибудь в коде инициализации:

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

Ответ 12

на основе этого подхода

true loop, полоса заголовка пейджера, true без обновления прокрутки

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

Ответ 13

Вы можете использовать простой вид из 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
            }
        });
    }
}