Usuń stronę fragmentu z ViewPager w systemie Android


138

Próbuję dynamicznie dodawać i usuwać fragmenty z ViewPager, dodawanie działa bez żadnych problemów, ale usuwanie nie działa zgodnie z oczekiwaniami.

Za każdym razem, gdy chcę usunąć bieżący element, ostatni zostaje usunięty.

Próbowałem również użyć FragmentStatePagerAdapter lub zwrócić POSITION_NONE w metodzie getItemPosition adaptera.

Co ja robię źle?

Oto podstawowy przykład:

MainActivity.java

public class MainActivity extends FragmentActivity implements TextProvider {

    private Button mAdd;
    private Button mRemove;
    private ViewPager mPager;

    private MyPagerAdapter mAdapter;

    private ArrayList<String> mEntries = new ArrayList<String>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mEntries.add("pos 1");
        mEntries.add("pos 2");
        mEntries.add("pos 3");
        mEntries.add("pos 4");
        mEntries.add("pos 5");

        mAdd = (Button) findViewById(R.id.add);
        mRemove = (Button) findViewById(R.id.remove);
        mPager = (ViewPager) findViewById(R.id.pager);

        mAdd.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                addNewItem();
            }
        });

        mRemove.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                removeCurrentItem();
            }
        });

        mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);

        mPager.setAdapter(mAdapter);

    }

    private void addNewItem() {
        mEntries.add("new item");
        mAdapter.notifyDataSetChanged();
    }

    private void removeCurrentItem() {
        int position = mPager.getCurrentItem();
        mEntries.remove(position);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public String getTextForPosition(int position) {
        return mEntries.get(position);
    }
    @Override
    public int getCount() {
        return mEntries.size();
    }


    private class MyPagerAdapter extends FragmentPagerAdapter {

        private TextProvider mProvider;

        public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
            super(fm);
            this.mProvider = provider;
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(mProvider.getTextForPosition(position));
        }

        @Override
        public int getCount() {
            return mProvider.getCount();
        }

    }

}

TextProvider.java

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}

MyFragment.java

public class MyFragment extends Fragment {

    private String mText;

    public static MyFragment newInstance(String text) {
        MyFragment f = new MyFragment(text);
        return f;
    }

    public MyFragment() {
    }

    public MyFragment(String text) {
        this.mText = text;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.fragment, container, false);

        ((TextView) root.findViewById(R.id.position)).setText(mText);

        return root;
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add new item" />

    <Button
        android:id="@+id/remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="remove current item" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="35sp" />

</LinearLayout>

2
+1 za kod źródłowy
wizurd

czy poprawne jest używanie konstruktora innego niż domyślny w MyFragment.java??
codeKiller

Odpowiedzi:


95

Rozwiązanie Loutha nie wystarczyło, aby wszystko działało dla mnie, ponieważ istniejące fragmenty nie ulegały zniszczeniu. Zmotywowany tą odpowiedzią stwierdziłem, że rozwiązaniem jest zastąpienie getItemId(int position)metody FragmentPagerAdapternadawania nowego unikalnego identyfikatora za każdym razem, gdy nastąpiła zmiana oczekiwanej pozycji fragmentu.

Kod źródłowy:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private TextProvider mProvider;
    private long baseId = 0;

    public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
        super(fm);
        this.mProvider = provider;
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(mProvider.getTextForPosition(position));
    }

    @Override
    public int getCount() {
        return mProvider.getCount();
    }


    //this is called when notifyDataSetChanged() is called
    @Override
    public int getItemPosition(Object object) {
        // refresh all fragments when data set changed
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public long getItemId(int position) {
        // give an ID different from position when position has been changed
        return baseId + position;
    }

    /**
     * Notify that the position of a fragment has been changed.
     * Create a new ID for each position to force recreation of the fragment
     * @param n number of items which have been changed
     */
    public void notifyChangeInPosition(int n) {
        // shift the ID returned by getItemId outside the range of all previous fragments
        baseId += getCount() + n;
    }
}

Teraz, na przykład, jeśli usuniesz pojedynczą kartę lub dokonasz jakiejś zmiany w kolejności, powinieneś zadzwonić notifyChangeInPosition(1)przed wywołaniem notifyDataSetChanged(), co zapewni, że wszystkie fragmenty zostaną odtworzone.

Dlaczego to rozwiązanie działa

Zastępowanie getItemPosition ():

Po notifyDataSetChanged()wywołaniu adapter wywołuje notifyChanged()metodę, do ViewPagerktórej jest przyłączony. Do ViewPagersprawdza wartość zwracana przez adapter na getItemPosition()każdej pozycji, usuwając te elementy, które powrotne POSITION_NONE(patrz kod źródłowy ), a następnie zasiedlających.

Zastępowanie getItemId ():

Jest to konieczne, aby zapobiec przeładowaniu przez adapter starego fragmentu podczas ViewPagerponownego zapełniania. Możesz łatwo zrozumieć, dlaczego to działa, patrząc na kod źródłowy dla instantiateItem () w FragmentPagerAdapter.

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }

Jak widać, getItem()metoda jest wywoływana tylko wtedy, gdy menedżer fragmentów nie znajdzie żadnych istniejących fragmentów o tym samym identyfikatorze. Wydaje mi się, że to błąd, że stare fragmenty są nadal dołączane nawet po notifyDataSetChanged()wywołaniu, ale dokumentacja ViewPagerwyraźnie stwierdza, że:

Uwaga: ta klasa jest obecnie na wczesnym etapie projektowania i rozwoju. Interfejs API prawdopodobnie ulegnie zmianie w późniejszych aktualizacjach biblioteki zgodności, co będzie wymagało zmian w kodzie źródłowym aplikacji, gdy są kompilowane względem nowszej wersji.

Miejmy więc nadzieję, że podane tutaj obejście nie będzie konieczne w przyszłej wersji biblioteki obsługi.


1
Dziękuję bardzo za tę odpowiedź! Próba rozwiązania tego problemu bez użycia FragmentStatePagerAdapter była wyczerpująca. Świetne wyjaśnienie. Czy znalazłeś sposób, aby wywołać getItem po powrocie do fragmentu? Próbowałem użyć twojego sprytnego notifyDataSetChanged () w różnych miejscach (np. OnStop) bez powodzenia
u2tall

Naprawdę nie rozumiem twojego pytania ... Opublikuj nowe pytanie wraz z kodem źródłowym, jeśli nie możesz tego rozgryźć.
Tim Rae

1
2016, to obejście jest nadal wymagane, aby notifyDataSetChanged()działać.
Subin Sebastian

Łał! Niesamowite wyjaśnienie. Miałem dość złożony viewpager, w którym dynamicznie usuwam i wstawiam elementy i nie uruchomiłbym go bez zrozumienia tego wszystkiego.
rupps

Nadal potrzebuję tego obejścia, aby zastąpić stary fragment. Cholera.
kopikaokao

291

ViewPager nie usuwa twoich fragmentów z powyższym kodem, ponieważ ładuje kilka widoków (lub fragmentów w twoim przypadku) do pamięci. Oprócz widocznego widoku ładuje również widok po obu stronach widocznego. Zapewnia to płynne przewijanie z widoku do widoku, co sprawia, że ​​ViewPager jest tak fajny.

Aby osiągnąć pożądany efekt, musisz zrobić kilka rzeczy.

  1. Zmień FragmentPagerAdapter na FragmentStatePagerAdapter. Powodem tego jest to, że FragmentPagerAdapter będzie przechowywać wszystkie widoki, które ładuje do pamięci na zawsze. Gdzie FragmentStatePagerAdapter usuwa widoki, które wykraczają poza bieżące i przechodzące widoki.

  2. Zastąp metodę adaptera getItemPosition (pokazaną poniżej). Kiedy wywołujemy mAdapter.notifyDataSetChanged();ViewPager, przesłuchuje adapter, aby określić, co się zmieniło w zakresie pozycjonowania. Używamy tej metody, aby powiedzieć, że wszystko się zmieniło, więc ponownie przetwórz całe swoje pozycjonowanie widoku.

A oto kod ...

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

}

1
OK, to wydaje się być najłatwiejszym obejściem tego problemu. Chociaż kilka innych obejść jest omawianych w raportach błędów tutaj: code.google.com/p/android/issues/detail?id=19110 i tutaj: code.google.com/p/android/issues/detail?id= 19001
Subin Sebastian

@Louth: używam ViewPager z TabsAdapter, rozszerzonym z FragmentStatePagerAdapter (na przykład na stronie odniesienia Google: developer.android.com/reference/android/support/v4/view/ ... ). Moje dodatkowe pytanie brzmi: jak najpierw usunąć kartę? Dzięki.
gcl1

4
@Louth Kiedy wywołujemy notifyDataSetChanged (), viewpager tworzy nowe fragmenty, ale starsze są nadal w pamięci. A moim problemem jest; otrzymują wezwanie do zdarzenia onOptionsItemSelected. Jak mogę pozbyć się starych fragmentów?
syloc

1
Dziękuję, samo zastąpienie FragmentPagerAdapter na FragmentStatePagerAdapter rozwiązało mój problem. Następnie po prostu usuwam AllViews () na ViewPager i dynamicznie ponownie ładuję fragmenty.
Michał

17
Dlaczego zawsze zwracasz POSITION_NONE zamiast naprawdę sprawdzać, czy dany widok / fragment / obiekt nadal istnieje i jest prawidłowy? Ta metoda ma odpowiedzieć na pytanie „hej, czy mogę ponownie użyć tego?” - i zawsze mówiąc „Nie!” oznacza po prostu odtworzenie wszystkiego! W wielu przypadkach jest to niepotrzebne.
Zordid

14

moje rozwiązanie robocze do usuwania strony fragmentów z pagera widoku

public class MyFragmentAdapter extends FragmentStatePagerAdapter {

    private ArrayList<ItemFragment> pages;

    public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
        super(fragmentManager);
        this.pages = pages;
    }

    @Override
    public Fragment getItem(int index) {
        return pages.get(index);
    }

    @Override
    public int getCount() {
        return pages.size();
    }

    @Override
    public int getItemPosition(Object object) {
        int index = pages.indexOf (object);

        if (index == -1)
            return POSITION_NONE;
        else
            return index;
    }
}

A kiedy muszę usunąć jakąś stronę według indeksu, robię to

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter

Oto inicjalizacja mojego adaptera

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);

Czy możesz udostępnić kod do dodania fragmentu?
VVB

Ale dodając to odzwierciedla powolne po dwukrotnym / trzykrotnym przesunięciu
VVB

jeśli nie usuwasz elementów, zwraca tylko element, nic więcej ... testujesz na wirtualnej maszynie czy prawdziwym urządzeniu?
Vasil Valchev

Wypróbuj inne rozwiązania, odpowiedzi z większą liczbą głosów. Wyświetl pager ma bardzo złożony system wypłat, który zapewnia lepszą wydajność podczas przesuwania, więc te hacki nie są obsługiwane przez oryginalny pager widoku w tym celu ...
Vasil Valchev

11

Fragment musi być już usunięty, ale problem dotyczył stanu zapisu przeglądarki

Próbować

myViewPager.setSaveFromParentEnabled(false);

Nic nie działało, ale to rozwiązało problem!

Twoje zdrowie !


2

Wpadłem na pomysł, aby po prostu skopiować kod źródłowy z android.support.v4.app.FragmentPagerAdpaterdo niestandardowej klasy o nazwie CustumFragmentPagerAdapter. Dało mi to możliwość zmodyfikowania tego instantiateItem(...)tak, aby za każdym razem, gdy jest wywoływany, usuwa / niszczy aktualnie dołączony fragment, zanim doda nowy fragment otrzymany z getItem()metody.

Po prostu zmodyfikuj instantiateItem(...)w następujący sposób:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);

    // remove / destroy current fragment
    if (fragment != null) {
        mCurTransaction.remove(fragment);
    }

    // get new fragment and add it
    fragment = getItem(position);
    mCurTransaction.add(container.getId(), fragment,    makeFragmentName(container.getId(), itemId));

    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

1

Możesz połączyć oba na lepsze:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){

      if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
 return PagerAdapter.POSITION_NONE;
    }
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.

}

Działa to tylko wtedy, gdy usuwasz skrajną prawą kartę, aby usunąć wewnętrzną lub lewą kartę, musisz zmienić kolejność kart i przywrócić nową pozycję zgodnie z dokumentacją developer.android.com/reference/android/support/v4 / view /…
BitByteDog,

1

Dla przyszłych czytelników!

Teraz możesz użyć ViewPager2 do dynamicznego dodawania, usuwania fragmentów z przeglądarki.

Dokumentacja API formularza cytowania

ViewPager2 zastępuje ViewPager, rozwiązując większość problemów jego poprzednika, w tym obsługę układu od prawej do lewej, orientację pionową, modyfikowalne kolekcje fragmentów itp.

Wziąć obejrzenia MutableCollectionFragmentActivity.ktw googlesample / Android viewpager2 na przykład dodanie, usunięcie fragmentów dynamicznie z viewpager.


Dla Twojej informacji:

Artykuły:

Dokumentacja API

Informacje o wydaniu

Repozytorium próbek: https://github.com/googlesamples/android-viewpager2


Mam 2 fragmenty mainfragment i otherfragment. Dodam kolejny fragment po każdej piątej pozycji i nie ma go na liście arraylistycznej, więc jak usunąć ten inny fragment, jeśli nie ma go na liście? Używam viewpager2
b devloper

0

Odpowiedź Loutha działa dobrze. Ale nie sądzę, że zawsze zwracanie pozycji POSITION_NONE to dobry pomysł. Ponieważ POSITION_NONE oznacza, że ​​ten fragment powinien zostać zniszczony i zostanie utworzony nowy fragment. Możesz to sprawdzić w funkcji dataSetChanged w kodzie źródłowym ViewPager.

        if (newPos == PagerAdapter.POSITION_NONE) {
            mItems.remove(i);
            i--;
            ... not related code
            mAdapter.destroyItem(this, ii.position, ii.object);

Więc myślę, że lepiej skorzystaj z tablicy zawierającej słabyReference, aby zachować wszystkie utworzone przez siebie fragmenty. A kiedy dodasz lub usuniesz jakąś stronę, możesz uzyskać właściwą pozycję z własnego arraylisty.

 public int getItemPosition(Object object) {
    for (int i = 0; i < mFragmentsReferences.size(); i ++) {
        WeakReference<Fragment> reference = mFragmentsReferences.get(i);
        if (reference != null && reference.get() != null) {
            Fragment fragment = reference.get();
            if (fragment == object) {
                return i;
            }
        }
    }
    return POSITION_NONE;
}

Zgodnie z komentarzami getItemPosition jest wywoływana, gdy widok hosta próbuje określić, czy pozycja elementu uległa zmianie. Wartość zwracana oznacza jego nową pozycję.

Ale to nie wystarczy. Nadal mamy ważny krok do zrobienia. W kodzie źródłowym FragmentStatePagerAdapter znajduje się tablica o nazwie „mFragments” przechowująca fragmenty, które nie są niszczone. I w funkcji instantiateItem.

if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }

Zwracał buforowany fragment bezpośrednio, gdy stwierdzi, że buforowany fragment nie jest zerowy. Więc jest problem. Na przykład usuńmy jedną stronę na pozycji 2. Po pierwsze, usuwamy ten fragment z naszej własnej tablicy referencyjnej. więc w getItemPosition zwróci POSITION_NONE dla tego fragmentu, a następnie ten fragment zostanie zniszczony i usunięty z "mFragments".

 mFragments.set(position, null);

Teraz fragment na pozycji 3 będzie na pozycji 2. I zostanie wywołany instantiatedItem z pozycją 3 parametru. W tej chwili trzecia pozycja w „mFramgents” nie jest pusta, więc zwróci bezpośrednio. Ale w rzeczywistości zwrócił fragment na pozycji 2. Więc kiedy przejdziemy do strony 3, znajdziemy tam pustą stronę.

Aby obejść ten problem. Radzę, że możesz skopiować kod źródłowy FragmentStatePagerAdapter do własnego projektu, a kiedy dodajesz lub usuwasz operacje, powinieneś dodawać i usuwać elementy z tablicy „mFragments”.

Sprawy będą prostsze, jeśli użyjesz po prostu PagerAdapter zamiast FragmentStatePagerAdapter. Powodzenia.


0

dynamicznie dodaje lub usuwa fragment w oknie podglądu.
Wywołaj setupViewPager (viewPager) przy rozpoczęciu aktywności. Aby załadować inny fragment, wywołaj setupViewPagerCustom (viewPager). np. wywołanie kliknięcia przycisku: setupViewPagerCustom (viewPager);

    private void setupViewPager(ViewPager viewPager)
{
    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFrag(new fragmnet1(), "HOME");
    adapter.addFrag(new fragmnet2(), "SERVICES");

    viewPager.setAdapter(adapter);
}

private void setupViewPagerCustom(ViewPager viewPager)
{

    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());

    adapter.addFrag(new fragmnet3(), "Contact us");
    adapter.addFrag(new fragmnet4(), "ABOUT US");


    viewPager.setAdapter(adapter);
}

//Viewpageradapter, handles the views

static class ViewPagerAdapter extends FragmentStatePagerAdapter
{
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager manager){
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

    public void addFrag(Fragment fragment, String title){
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position){
        return mFragmentTitleList.get(position);
    }
}

0

Miałem problemy z FragmentStatePagerAdapter. Po usunięciu elementu:

  • był inny przedmiot używany do pozycji (przedmiot, który nie należał do pozycji, ale do pozycji obok niej)
  • lub jakiś fragment nie został załadowany (na tej stronie było widoczne tylko puste tło)

Po wielu eksperymentach wpadłem na następujące rozwiązanie.

public class SomeAdapter extends FragmentStatePagerAdapter {

    private List<Item> items = new ArrayList<Item>();

    private boolean removing;

    @Override
    public Fragment getItem(int position) {
        ItemFragment fragment = new ItemFragment();
        Bundle args = new Bundle();
        // use items.get(position) to configure fragment
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public int getItemPosition(Object object) {
        if (removing) {
            return PagerAdapter.POSITION_NONE;
        }

        Item item = getItemOfFragment(object);

        int index = items.indexOf(item);
        if (index == -1) {
            return POSITION_NONE;
        } else {
            return index;
        }
    }

   public void addItem(Item item) {
        items.add(item);
        notifyDataSetChanged();
    }

    public void removeItem(int position) {
        items.remove(position);

        removing = true;
        notifyDataSetChanged();
        removing = false;
    }
}

To rozwiązanie wykorzystuje hack tylko w przypadku usunięcia przedmiotu. W przeciwnym razie (np. Podczas dodawania pozycji) zachowuje czystość i wydajność oryginalnego kodu.

Oczywiście z zewnątrz adaptera wywołujesz tylko addItem / removeItem, nie ma potrzeby wywoływania notifyDataSetChanged ().


0

Wypróbuj to rozwiązanie. Użyłem wiązania danych do powiązania widoku. Możesz użyć wspólnej funkcji „findViewById ()”.

public class ActCPExpense extends BaseActivity implements View.OnClickListener,  {
private static final String TAG = ActCPExpense.class.getSimpleName();
private Context mContext;
private ActCpLossBinding mBinding;

private ViewPagerAdapter adapter;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        setContentView(R.layout.act_cp_loss);
        mBinding = DataBindingUtil.setContentView(this, R.layout.act_cp_loss);
        mContext = ActCPExpense.this;
        initViewsAct();


    } catch (Exception e) {
        LogUtils.LOGE(TAG, e);
    }

}


private void initViewsAct() {

    adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFragment(FragmentCPPayee.newInstance(), "Title");
    mBinding.viewpager.setAdapter(adapter);
    mBinding.tab.setViewPager(mBinding.viewpager);



}



@Override
public boolean onOptionsItemSelected(MenuItem itemActUtility) {
    int i = itemActUtility.getItemId();
    if (i == android.R.id.home) {
        onBackPressed();

    } 

    return super.onOptionsItemSelected(itemActUtility);
}

@Override
public void onClick(View view) {
    super.onClick(view);
    int id = view.getId();
    if (id == R.id.btnAdd) {
        addFragment();

    } else if (id == R.id.btnDelete) {
        removeFragment();

    }


}
    private void addFragment(){
    adapter.addFragment(FragmentCPPayee.newInstance("Title");
    adapter.notifyDataSetChanged();
    mBinding.tab.setViewPager(mBinding.viewpager);
}
private void removeFragment(){
    adapter.removeItem(mBinding.viewpager.getCurrentItem());
    mBinding.tab.setViewPager(mBinding.viewpager);
}


class ViewPagerAdapter extends FragmentStatePagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();


    public ViewPagerAdapter(FragmentManager manager) {
        super(manager);
    }

    @Override
    public int getItemPosition(@NonNull Object object) {
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }


    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);

    }

    public void removeItem(int pos) {

        destroyItem(null, pos, mFragmentList.get(pos));
        mFragmentList.remove(pos);
        mFragmentTitleList.remove(pos);
        adapter.notifyDataSetChanged();
        mBinding.viewpager.setCurrentItem(pos - 1, false);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "Title " + String.valueOf(position + 1);
    }
}


}

0

Dodałem funkcję „clearFragments” i użyłem tej funkcji do wyczyszczenia adaptera przed ustawieniem nowych fragmentów. To wywołuje odpowiednie akcje usuwania fragmentów. Moja klasa pagerAdapter:

private class ChartPagerAdapter extends FragmentPagerAdapter{
    private ArrayList<Fragment> fragmentList;

    ChartPagerAdapter(FragmentManager fm){
        super(fm);
        fragmentList = new ArrayList<>();
    }

    void setFragments(ArrayList<? extends Fragment> fragments){
        fragmentList.addAll(fragments);
    }

    void clearFragments(){
        for(Fragment fragment:fragmentList)
            getChildFragmentManager().beginTransaction().remove(fragment).commit();
        fragmentList.clear();
    }

    @Override
    public Fragment getItem(int i) {
        return fragmentList.get(i);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

}

0

rozwiązałem ten problem, wykonując te czynności

1- użyj FragmentPagerAdapter

2- w każdym fragmencie utwórz losowy identyfikator

fragment.id = new Random().nextInt();

3- Zastąp getItemPosition w adapterze

@Override
public int getItemPosition(@NonNull Object object) {
    return PagerAdapter.POSITION_NONE;
}

4 zastąpienie getItemId w adapterze

 @Override
public long getItemId(int position) {
    return mDatasetFragments.get(position).id;
}

5- teraz usuń kod jest

 adapter.mDatasetFragments.remove(< item to delete position >);
 adapter.notifyDataSetChanged();

to zadziałało dla mnie, mam nadzieję, że pomoże


0

Kod mojej ostatecznej wersji, naprawił WSZYSTKIE błędy. Zajęło mi to 3 dni

Zaktualizowano 2018/07/18: Zmieniłem wiele kodu źródłowego i naprawiłem tak wiele błędów, ale nie obiecuję, że nadal będzie działać.

https://github.com/lin1987www/FragmentBuilder/blob/master/commonLibrary/src/main/java/android/support/v4/app/FragmentStatePagerAdapterFix.java

public class FragmentStatePagerAdapterFix extends PagerAdapter {
    private static final String TAG = FragmentStatePagerAdapterFix.class.getSimpleName();
    private static final boolean DEBUG = false;

    private WeakReference<FragmentActivity> wrFragmentActivity;
    private WeakReference<Fragment> wrParentFragment;
    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;

    protected ArrayList<Fragment> mFragments = new ArrayList<>();
    protected ArrayList<FragmentState> mFragmentStates = new ArrayList<>();
    protected ArrayList<String> mFragmentTags = new ArrayList<>();
    protected ArrayList<String> mFragmentClassNames = new ArrayList<>();
    protected ArrayList<Bundle> mFragmentArgs = new ArrayList<>();

    private Fragment mCurrentPrimaryItem = null;
    private boolean[] mTempPositionChange;

    @Override
    public int getCount() {
        return mFragmentClassNames.size();
    }

    public FragmentActivity getFragmentActivity() {
        return wrFragmentActivity.get();
    }

    public Fragment getParentFragment() {
        return wrParentFragment.get();
    }

    public FragmentStatePagerAdapterFix(FragmentActivity activity) {
        mFragmentManager = activity.getSupportFragmentManager();
        wrFragmentActivity = new WeakReference<>(activity);
        wrParentFragment = new WeakReference<>(null);
    }

    public FragmentStatePagerAdapterFix(Fragment fragment) {
        mFragmentManager = fragment.getChildFragmentManager();
        wrFragmentActivity = new WeakReference<>(fragment.getActivity());
        wrParentFragment = new WeakReference<>(fragment);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass) {
        add(fragClass, null, null);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args) {
        add(fragClass, args, null);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, String tag) {
        add(fragClass, null, tag);
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag) {
        add(fragClass, args, tag, getCount());
    }

    public void add(Class<? extends android.support.v4.app.Fragment> fragClass, Bundle args, String tag, int position) {
        mFragments.add(position, null);
        mFragmentStates.add(position, null);
        mFragmentTags.add(position, tag);
        mFragmentClassNames.add(position, fragClass.getName());
        mFragmentArgs.add(position, args);
        mTempPositionChange = new boolean[getCount()];
    }

    public void remove(int position) {
        if (position < getCount()) {
            mTempPositionChange = new boolean[getCount()];
            for (int i = position; i < mTempPositionChange.length; i++) {
                mTempPositionChange[i] = true;
            }
            mFragments.remove(position);
            mFragmentStates.remove(position);
            mFragmentTags.remove(position);
            mFragmentClassNames.remove(position);
            mFragmentArgs.remove(position);
        }
    }

    public void clear(){
        mFragments.clear();
        mFragmentStates.clear();
        mFragmentTags.clear();
        mFragmentClassNames.clear();
        mFragmentArgs.clear();
    }

    @Override
    public void startUpdate(ViewGroup container) {
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment;
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        fragment = mFragments.get(position);
        if (fragment != null) {
            return fragment;
        }
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        FragmentState fs = mFragmentStates.get(position);
        if (fs != null) {
            fragment = fs.instantiate(getFragmentActivity(), getParentFragment());
            // Fix bug
            // http://stackoverflow.com/questions/11381470/classnotfoundexception-when-unmarshalling-android-support-v4-view-viewpagersav
            if (fragment.mSavedFragmentState != null) {
                fragment.mSavedFragmentState.setClassLoader(fragment.getClass().getClassLoader());
            }
        }
        if (fragment == null) {
            fragment = Fragment.instantiate(getFragmentActivity(), mFragmentClassNames.get(position), mFragmentArgs.get(position));
        }
        if (DEBUG) {
            Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mFragmentStates.set(position, null);
        mCurTransaction.add(container.getId(), fragment, mFragmentTags.get(position));
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) {
            Log.v(TAG, "Removing item #" + position + ": f=" + object
                    + " v=" + ((Fragment) object).getView());
        }
        if (position < getCount()) {
            FragmentState fragmentState = new FragmentState(fragment);
            Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment);
            if (savedState != null) {
                fragmentState.mSavedFragmentState = savedState.mState;
            }
            mFragmentStates.set(position, fragmentState);
            mFragments.set(position, null);
        }
        mCurTransaction.remove(fragment);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
            // Fix: Fragment is added by transaction. BUT didn't add to FragmentManager's mActive.
            for (Fragment fragment : mFragments) {
                if (fragment != null) {
                    fixActiveFragment(mFragmentManager, fragment);
                }
            }
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        Bundle state = null;
        // 目前顯示的 Fragments
        for (int i = 0; i < mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        if (mFragmentStates.size() > 0) {
            if (state == null) {
                state = new Bundle();
            }
            FragmentState[] fs = new FragmentState[mFragmentStates.size()];
            mFragmentStates.toArray(fs);
            state.putParcelableArray("states_fragment", fs);
        }
        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle) state;
            bundle.setClassLoader(loader);
            Parcelable[] fs = bundle.getParcelableArray("states_fragment");
            mFragments.clear();
            mFragmentStates.clear();
            mFragmentTags.clear();
            mFragmentClassNames.clear();
            mFragmentArgs.clear();
            if (fs != null) {
                for (int i = 0; i < fs.length; i++) {
                    FragmentState fragmentState = (FragmentState) fs[i];
                    mFragmentStates.add(fragmentState);
                    if (fragmentState != null) {
                        mFragmentArgs.add(fragmentState.mArguments);
                        mFragmentTags.add(fragmentState.mTag);
                        mFragmentClassNames.add(fragmentState.mClassName);
                    } else {
                        mFragmentArgs.add(null);
                        mFragmentTags.add(null);
                        mFragmentClassNames.add(null);
                    }
                    mFragments.add(null);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key : keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                        mFragmentArgs.set(index, f.mArguments);
                        mFragmentTags.set(index, f.mTag);
                        mFragmentClassNames.set(index, f.getClass().getName());
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
            // If restore will change
            notifyDataSetChanged();
        }
    }

    public static void fixActiveFragment(FragmentManager fragmentManager, Fragment fragment) {
        FragmentManagerImpl fm = (FragmentManagerImpl) fragmentManager;
        if (fm.mActive != null) {
            int index = fragment.mIndex;
            Fragment origin = fm.mActive.get(index);
            if (origin != null) {
                if ((origin.mIndex != fragment.mIndex) || !(origin.equals(fragment))) {
                    Log.e(TAG,
                            String.format("fixActiveFragment: Not Equal! Origin: %s %s, Fragment: %s $s",
                                    origin.getClass().getName(), origin.mIndex,
                                    fragment.getClass().getName(), fragment.mIndex
                            ));
                }
            }
            fm.mActive.set(index, fragment);
        }
    }

    // Fix
    // http://stackoverflow.com/questions/10396321/remove-fragment-page-from-viewpager-in-android
    @Override
    public int getItemPosition(Object object) {
        int index = mFragments.indexOf(object);
        if (index < 0) {
            return PagerAdapter.POSITION_NONE;
        }
        boolean isPositionChange = mTempPositionChange[index];
        int result = PagerAdapter.POSITION_UNCHANGED;
        if (isPositionChange) {
            result = PagerAdapter.POSITION_NONE;
        }
        return result;
    }
}

Mam 2 fragmenty mainfragment i otherfragment. Dodam kolejny fragment po każdej piątej pozycji i nie ma go na liście arraylistycznej, więc jak usunąć ten inny fragment, jeśli nie ma go na liście? Używam viewpager2
b devloper

link do github nie działa
b devloper

-1

Możesz po prostu zastąpić destroyItemmetodę

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
}

-9

Mam nadzieję, że to pomoże ci, co chcesz.

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_UNCHANGED;
    }
}

9
to jest dokładnie odwrotne do zaakceptowanej odpowiedzi - jak to działa?
Zordid,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.