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>
MyFragment.java
??Odpowiedzi:
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)
metodyFragmentPagerAdapter
nadawania nowego unikalnego identyfikatora za każdym razem, gdy nastąpiła zmiana oczekiwanej pozycji fragmentu.Kod źródłowy:
Teraz, na przykład, jeśli usuniesz pojedynczą kartę lub dokonasz jakiejś zmiany w kolejności, powinieneś zadzwonić
notifyChangeInPosition(1)
przed wywołaniemnotifyDataSetChanged()
, co zapewni, że wszystkie fragmenty zostaną odtworzone.Dlaczego to rozwiązanie działa
Zastępowanie getItemPosition ():
Po
notifyDataSetChanged()
wywołaniu adapter wywołujenotifyChanged()
metodę, doViewPager
której jest przyłączony. DoViewPager
sprawdza wartość zwracana przez adapter nagetItemPosition()
każdej pozycji, usuwając te elementy, które powrotnePOSITION_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
ViewPager
ponownego zapełniania. Możesz łatwo zrozumieć, dlaczego to działa, patrząc na kod źródłowy dla instantiateItem () wFragmentPagerAdapter
.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 ponotifyDataSetChanged()
wywołaniu, ale dokumentacjaViewPager
wyraźnie stwierdza, że:Miejmy więc nadzieję, że podane tutaj obejście nie będzie konieczne w przyszłej wersji biblioteki obsługi.
źródło
notifyDataSetChanged()
działać.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.
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.
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 ...
źródło
moje rozwiązanie robocze do usuwania strony fragmentów z pagera widoku
A kiedy muszę usunąć jakąś stronę według indeksu, robię to
Oto inicjalizacja mojego adaptera
źródło
Fragment musi być już usunięty, ale problem dotyczył stanu zapisu przeglądarki
Próbować
Nic nie działało, ale to rozwiązało problem!
Twoje zdrowie !
źródło
Wpadłem na pomysł, aby po prostu skopiować kod źródłowy z
android.support.v4.app.FragmentPagerAdpater
do niestandardowej klasy o nazwieCustumFragmentPagerAdapter
. Dało mi to możliwość zmodyfikowania tegoinstantiateItem(...)
tak, aby za każdym razem, gdy jest wywoływany, usuwa / niszczy aktualnie dołączony fragment, zanim doda nowy fragment otrzymany zgetItem()
metody.Po prostu zmodyfikuj
instantiateItem(...)
w następujący sposób:źródło
Możesz połączyć oba na lepsze:
źródło
Dla przyszłych czytelników!
Teraz możesz użyć ViewPager2 do dynamicznego dodawania, usuwania fragmentów z przeglądarki.
Dokumentacja API formularza cytowania
Wziąć obejrzenia
MutableCollectionFragmentActivity.kt
w 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
źródło
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.
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.
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.
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".
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.
źródło
źródło
Miałem problemy z
FragmentStatePagerAdapter
. Po usunięciu elementu:Po wielu eksperymentach wpadłem na następujące rozwiązanie.
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 ().
źródło
Wypróbuj to rozwiązanie. Użyłem wiązania danych do powiązania widoku. Możesz użyć wspólnej funkcji „findViewById ()”.
źródło
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:
źródło
rozwiązałem ten problem, wykonując te czynności
1- użyj FragmentPagerAdapter
2- w każdym fragmencie utwórz losowy identyfikator
3- Zastąp getItemPosition w adapterze
4 zastąpienie getItemId w adapterze
5- teraz usuń kod jest
to zadziałało dla mnie, mam nadzieję, że pomoże
źródło
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
źródło
Możesz po prostu zastąpić
destroyItem
metodęźródło
Mam nadzieję, że to pomoże ci, co chcesz.
źródło