J'utilise un ViewPager
avec le FragmentStatePagerAdapter
pour permettre la navigation entre certains fragments.
Disons que j'ai trois fragments: A
, B
et C
. Le ViewPager affiche initialement le fragment A et vous permet de naviguer vers le fragment B en faisant glisser de droite à gauche, puis vers le fragment C en faisant glisser à nouveau. Cela permet les chemins de navigation suivants: A <--> B <--> C
.
Ce que je voudrais, c'est pouvoir balayer de gauche à droite sur le fragment A et que le ViewPager affiche le fragment C, c'est-à-dire pour qu'il se comporte comme une file d'attente circulaire et autorise ... --> C <--> A <--> B <--> C <--> A <-- ...
Je ne veux pas que les fragments soient dupliqués dans d'autres positions (c'est-à-dire se retrouver avec plus de trois instances).
Cette fonctionnalité d'encapsulation est-elle possible avec un ViewPager?
J'ai implémenté un ViewPager/PagerAdapter qui peut permettre un comportement de pagination pseudo-infini. Il fonctionne en spécifiant un très grand nombre comme nombre réel, mais les mappe à la plage réelle de l'ensemble de données/ensemble de pages. Il décale également le début d'un grand nombre afin que vous puissiez immédiatement faire défiler vers la gauche à partir de la "première" page.
Cela ne fonctionne pas si bien une fois que vous êtes à la 1 000 000e page (vous verrez des problèmes graphiques lors du défilement), mais ce n'est généralement pas un cas d'utilisation réel. Je pourrais résoudre ce problème en réinitialisant le compte à un nombre inférieur de temps en temps, mais je vais le laisser tel qu'il est pour l'instant.
InfinitePagerAdapter
encapsule un ViewPager existant, et donc l'utilisation est assez transparente. InfiniteViewPager
fait un peu de travail pour vous assurer que vous pouvez potentiellement faire défiler vers la gauche et la droite plusieurs fois.
Ceci est une solution sans fausses pages et fonctionne comme un charme:
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) {
}
}
Il vous suffit de le définir sur votre ViewPager en tant que onPageChangeListener et c'est tout: (** obsolète maintenant ** vérifiez les notes d'édition)
viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));
Pour éviter d'avoir cet éclat bleu à la "fin" de votre ViewPager, vous devez appliquer cette ligne à votre xml où le ViewPager est placé:
Android:overScrollMode="never"
Nous avons amélioré la solution ci-dessus et créé une petite bibliothèque sur github . N'hésitez pas à y jeter un œil :)
Edit :: Selon le commentaire @Bhavana, utilisez simplement addOnPageChangeListener au lieu de setOnPageChangeListener car plus tard est obsolète.
viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));
Je viens de voir cette question et j'ai trouvé une solution. Lien de solution
Apportez les modifications suivantes à votre fonction onPageScrollStateChanged. Considérez que vous avez 4 onglets .
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);
}
}
}
Si la variable d'état se trouve dans les onglets existants, sa valeur est 1.
Il devient 0 une fois que vous essayez de naviguer avant votre premier onglet ou après votre dernier onglet. Donc, comparez les états précédent et actuel, comme indiqué dans le code et vous avez terminé.
Voir la fonction onPageScrollStateChanged ici .
J'espère que ça aide.
Une autre façon de procéder consiste à ajouter une fausse copie du premier élément du pager de vue à la fin de votre adaptateur et une fausse copie du dernier élément au début. Ensuite, lorsque vous affichez le premier/dernier élément du pager de vue, changez de page sans animation.
Donc, fondamentalement, les premiers/derniers éléments de votre pager de vue sont des faux, juste là pour produire l'animation correcte, puis basculez vers la fin/début. Exemple:
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);
}
}
}
Cela accomplit presque ce que veut antonyt, mais cela ne vous laissera pas passer de A -> C. (Non testé vigoureusement, mais semble fonctionner assez bien)
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
}
}
J'ai essayé toutes les suggestions, solutions, bibliothèques, etc. mais elles ne sont pas purement circulaires et la plupart du temps ne prennent en charge que 3 pages.
J'ai donc implémenté un exemple circulaire ViewPager
en utilisant le nouveau ViewPager2
, Le nouveau ViewPager
utilise un RecyclerView
et ViewHolder
s pour gérer le recyclage des vues et fonctionne comme prévu!
TLDR: GITHUB
Dans cet exemple, va créer une application d'activité unique avec ViewPager2
Et un FragmentPagerAdapter
supportant la navigation circulaire entre 3 pages ou plus.
J'utilise une version alpha de la bibliothèque androidx.viewpager2:viewpager2
, Mais la version 1.0.0-alpha06
Est la dernière prévue avant que google ne bloque l'API et passe en version bêta.
1. Ajoutez la bibliothèque ViewPager2
Aux dépendances dans votre build.gradle
dependencies {
implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06'
}
2. Ajoutez la vue ViewPager2
À votre projet:
<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. Créez l'adaptateur FragmentStateAdapter
:
getItemCount()
doit retourner un nombre huuuuge. (2147483647)
getCenterPage()
renvoie la page centrale en fonction du nombre huuuuge. Cette méthode est utilisée pour obtenir la position de la page initiale à définir dans le ViewPager2
, Dans ce cas, l'utilisateur doit glisser ˜1073741823 pour atteindre la fin du 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 est un ENUM avec les informations de la page.
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. Définissez l'adaptateur sur le ViewPager
val circularAdapter = CircularPagerAdapter(supportFragmentManager, lifecycle)
vwpHome.apply {
adapter = circularAdapter
setCurrentItem(circularAdapter.getCenterPage(), false)
}
Idée simple pour y parvenir. Lorsque vous avez votre tableau avec des pages, ajoutez simplement ce tableau à un autre tableau exactement au milieu.
Par exemple, vous avez un tableau avec 15 éléments. Placez-le donc dans un tableau de 400 éléments au milieu (position 200)
Ensuite, remplissez simplement le tableau à gauche et à droite pour pouvoir vous déplacer.
Exemple d'image:
À quoi ça ressemble?
Laissez parler le code :)
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;
}
J'ai une solution qui, je pense, fonctionnera. Ce n'est pas testé, mais le voici:
Un wrapper autour de FragmentPagerAdapter comme super classe:
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);
};
Ensuite, l'implémentation standard de FragmentPagerAdapter sous-classe cette nouvelle classe abstraite:
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; }
};
Et l'implémentation de l'adaptateur Cyclic Pager.
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;
}
};
Initialisation de ViewPager
this.mViewPager.setCurrentItem(this.mPagerAdapter.getStartPosition());
Et enfin, l'implémentation du rappel:
@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));
}
Vous pouvez utiliser une vue simple de https://github.com/pozitiffcat/cyclicview
L'une des fonctionnalités est:
Ne dupliquez pas les première et dernière vues, utilisez plutôt les variantes bitmap
Exemple:
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
}
});
}
}
On onCreate (Bundle savedInstanceState) définir l'élément actuel pour la page 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);
}
}
Désolé pour le retard, mais j'ai vu les réponses et je n'ai pas pu tenir, j'ai dû y répondre aussi :).
Sur votre adaptateur, sur la méthode get count, procédez comme suit:
@Override
public int getCount() {
return myList.size() % Integer.MAX_VALUE;
}
Ça marchera!
sélectionnez le véritable premier élément quelque part dans le code init:
mViewPager.setCurrentItem(1);
modifier 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);
}
}
basé sur cette approche
boucle vraie, bande de titre du pager, défilement sans rafraîchissement vrai
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);
}
afficher l'adaptateur de page:
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;
}
}
en utilisant:
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;
}
}
});