Próbuję użyć Fragment za ViewPager
pomocą FragmentPagerAdapter
. To, czego szukam, to zamiana fragmentu umieszczonego na pierwszej stronie na ViewPager
inny.
Pager składa się z dwóch stron. Pierwszym z nich jest FirstPagerFragment
, drugim jest SecondPagerFragment
. Kliknięcie przycisku na pierwszej stronie. Chciałbym zamienić FirstPagerFragment
na NextFragment.
Oto mój kod poniżej.
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
... a tutaj pliki xml
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
Teraz problem ... w którym identyfikatorze powinienem użyć
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
Jeśli użyję R.id.first_fragment_root_id
, zamiana działa, ale Hierarchy Viewer pokazuje dziwne zachowanie, jak poniżej.
Na początku jest tak
po wymianie sytuacja wygląda następująco
Jak widać, coś jest nie tak, spodziewam się znaleźć taki sam stan, jak na pierwszym zdjęciu po zastąpieniu fragmentu.
Odpowiedzi:
Istnieje inne rozwiązanie, które nie wymaga modyfikacji kodu źródłowego
ViewPager
iFragmentStatePagerAdapter
, i działa zFragmentPagerAdapter
klasą podstawową używaną przez autora.Chciałbym zacząć od odpowiedzi na pytanie autora dotyczące tego, z którego identyfikatora powinien korzystać; jest to identyfikator kontenera, tj. identyfikator samego pagera widokowego. Jednak, jak zapewne zauważyłeś, użycie tego identyfikatora w kodzie powoduje, że nic się nie dzieje. Wyjaśnię dlaczego:
Po pierwsze, aby
ViewPager
ponownie zapełnić strony, musisz wywołaćnotifyDataSetChanged()
rezydującą w klasie bazowej adaptera.Po drugie,
ViewPager
używagetItemPosition()
metody abstrakcyjnej, aby sprawdzić, które strony powinny zostać zniszczone, a które należy zachować. Domyślna implementacja tej funkcji zawsze zwraca wartośćPOSITION_UNCHANGED
, co powodujeViewPager
zachowanie wszystkich bieżących stron, aw konsekwencji nie dołączenie nowej strony. Dlatego, aby zastąpienie fragmentu działało,getItemPosition()
musi zostać zastąpione w adapterze i musi powrócić,POSITION_NONE
gdy zostanie wywołane ze starym, ukrytym, fragmentem jako argumentem.Oznacza to również, że twój adapter zawsze musi wiedzieć, który fragment powinien być wyświetlany w pozycji 0
FirstPageFragment
lubNextFragment
. Jednym ze sposobów na to jest dostarczenie detektora podczas tworzeniaFirstPageFragment
, który zostanie wywołany, gdy nadejdzie czas na zmianę fragmentów. Myślę, że to dobra rzecz, aby umożliwić adapterowi fragmentów obsługę wszystkich przełączników fragmentów i wywołań doViewPager
iFragmentManager
.Po trzecie,
FragmentPagerAdapter
buforuje używane fragmenty nazwą pochodzącą od pozycji, więc jeśli na pozycji 0 istniał fragment, nie zostanie on zastąpiony, nawet jeśli klasa jest nowa. Istnieją dwa rozwiązania, ale najprostszym jest użycieremove()
funkcjiFragmentTransaction
, która również usunie jego tag.To było dużo tekstu, oto kod, który powinien działać w twoim przypadku:
Mam nadzieję, że to pomoże każdemu!
źródło
FirstPageFragment.newInstance()
parametr listener?Od 13 listopada 2012 r. Wydaje się, że zmiana fragmentów w ViewPager stała się znacznie łatwiejsza. Google wypuścił system Android 4.2 z obsługą zagnieżdżonych fragmentów, a także jest obsługiwany w nowej bibliotece obsługi systemu Android v11, więc będzie to działało aż do wersji 1.6
Jest to bardzo podobne do normalnego sposobu zastępowania fragmentu, z wyjątkiem użycia getChildFragmentManager. Wydaje się, że działa, ale zagnieżdżony fragment backstack nie jest wyskakujący, gdy użytkownik kliknie przycisk Wstecz. Zgodnie z rozwiązaniem w tym połączonym pytaniu należy ręcznie wywołać funkcję popBackStackImmediate () w menedżerze podrzędnym fragmentu. Musisz więc zastąpić funkcję onBackPressed () działania ViewPager, w której uzyskasz bieżący fragment ViewPager i wywołasz na nim funkcję getChildFragmentManager (). PopBackStackImmediate ().
Uzyskiwanie aktualnie wyświetlanego fragmentu jest również trochę hackerskie, użyłem tego brudnego rozwiązania „android: switcher: VIEWPAGER_ID: INDEX”, ale możesz również samodzielnie śledzić wszystkie fragmenty ViewPager, jak wyjaśniono w drugim rozwiązaniu na tej stronie .
Oto mój kod dla ViewPager z 4 ListViews z widokiem szczegółowym pokazanym w ViewPager, gdy użytkownik kliknie wiersz i przy działającym przycisku Wstecz. Próbowałem dołączyć tylko odpowiedni kod ze względu na zwięzłość, więc zostaw komentarz, jeśli chcesz przesłać pełną aplikację do GitHub.
HomeActivity.java
ListProductsFragment.java
źródło
Na podstawie odpowiedzi @wize, która okazała się pomocna i elegancka, mogłem częściowo osiągnąć to, czego chciałem, ponieważ chciałem, aby możliwość powrotu do pierwszego fragmentu po wymianie. Osiągnąłem to trochę modyfikując nieco jego kod.
Byłby to FragmentPagerAdapter:
Aby wykonać zamianę, po prostu zdefiniuj pole statyczne typu
CalendarPageFragmentListener
i zainicjowanenewInstance
metodami odpowiednich fragmentów i wywołajFirstFragment.pageListener.onSwitchToNextFragment()
lubNextFragment.pageListener.onSwitchToNextFragment()
ponownie.źródło
mFragmentAtPos0
odniesienie podczas zapisywania stanu aktywności. To nie jest najbardziej eleganckie rozwiązanie, ale działa.Wdrożyłem rozwiązanie dla:
Aby to osiągnąć, sztuczki są następujące:
Kod adaptera jest następujący:
Przy pierwszym dodaniu wszystkich kart musimy wywołać metodę createHistory (), aby utworzyć początkową historię
Za każdym razem, gdy chcesz zastąpić fragment do określonej karty, którą wywołujesz: replace (końcowa pozycja int, końcowy fragment klasyClass, końcowe argumenty pakietu)
Po wciśnięciu back musisz wywołać metodę back ():
Rozwiązanie działa z sherlockowym paskiem akcji i gestem przeciągnięcia.
źródło
tl; dr: Użyj fragmentu hosta, który jest odpowiedzialny za zastąpienie hostowanej zawartości i śledzi historię nawigacji wstecznej (jak w przeglądarce).
Ponieważ twój przypadek użycia składa się ze stałej liczby zakładek, moje rozwiązanie działa dobrze: Chodzi o to, aby wypełnić ViewPager instancjami niestandardowej klasy
HostFragment
, która jest w stanie zastąpić hostowaną zawartość i zachowuje własną historię nawigacji wstecz. Aby zastąpić hostowany fragment, wywołujesz metodęhostfragment.replaceFragment()
:Wszystko, co robi ta metoda, to zastąpienie układu ramki idem
R.id.hosted_fragment
fragmentem dostarczonym do metody.Sprawdź mój samouczek na ten temat, aby uzyskać dalsze szczegóły i pełny działający przykład na GitHub!
źródło
Niektóre z przedstawionych rozwiązań bardzo pomogły mi częściowo rozwiązać problem, ale wciąż brakuje jednej ważnej rzeczy w rozwiązaniach, które spowodowały nieoczekiwane wyjątki i zawartość czarnej strony zamiast fragmentacji treści w niektórych przypadkach.
Chodzi o to, że klasa FragmentPagerAdapter używa ID elementu do przechowywania buforowanych fragmentów w FragmentManager . Z tego powodu należy również przesłonić metodę getItemId (int position) , aby zwracała ona np. Pozycję dla stron najwyższego poziomu i pozycję 100+ dla stron ze szczegółami. W przeciwnym razie wcześniej utworzony fragment najwyższego poziomu zostałby zwrócony z pamięci podręcznej zamiast fragmentu poziomu szczegółowego.
Ponadto dzielę się tutaj kompletnym przykładem, jak zaimplementować działanie podobne do kart ze stronami fragmentów za pomocą ViewPager i przycisków kart za pomocą RadioGroup, która umożliwia zastąpienie stron najwyższego poziomu szczegółowymi stronami, a także obsługuje przycisk Wstecz. Ta implementacja obsługuje tylko jeden poziom układania wstecznego (lista elementów - szczegóły pozycji), ale wielopoziomowa implementacja układania wstecznego jest prosta. Ten przykład działa całkiem dobrze w normalnych przypadkach, z wyjątkiem tego, że generuje wyjątek NullPointerException w przypadku, gdy przełączasz się np. Na drugą stronę, zmieniasz fragment pierwszej strony (chociaż nie jest widoczny) i powracasz do pierwszej strony. Kiedy to rozwiążę, opublikuję rozwiązanie tego problemu:
źródło
Utworzyłem ViewPager z 3 elementami i 2 elementami podrzędnymi dla indeksu 2 i 3 i tutaj chciałem zrobić ..
Zaimplementowałem to przy pomocy poprzednich pytań i odpowiedzi StackOverFlow i oto link.
ViewPagerChildFragments
źródło
Aby zastąpić fragment wewnątrz
ViewPager
, możesz przenieść kody źródłoweViewPager
,PagerAdapter
iFragmentStatePagerAdapter
klas do projektu i dodać następujący kod.w
ViewPager
:w FragmentStatePagerAdapter:
handleGetItemInvalidated()
zapewnia, że po następnym wywołaniugetItem()
zwróci newFragmentgetFragmentPosition()
zwraca pozycję fragmentu w adapterze.Teraz, aby zastąpić fragmenty, zadzwoń
Jeśli interesuje Cię przykładowy projekt, zapytaj mnie o źródła.
źródło
Działa świetnie z rozwiązaniem AndroidTeam, jednak stwierdziłem, że potrzebowałem możliwości cofnięcia się w podobny sposób.
FrgmentTransaction.addToBackStack(null)
Jednak samo dodanie tego spowoduje tylko zastąpienie fragmentu bez powiadamiania ViewPager. Połączenie dostarczonego rozwiązania z tym niewielkim ulepszeniem pozwoli ci powrócić do poprzedniego stanu, po prostu zastępująconBackPressed()
metodę działania. Największą wadą jest to, że cofnie się tylko raz na raz, co może spowodować wielokrotne kliknięcia wsteczMam nadzieję, że to komuś pomoże.
Również w miarę możliwości
getFragmentPosition()
jestgetItem()
odwrotnie. Wiesz, które fragmenty idą gdzieś, po prostu upewnij się, że zwrócisz prawidłową pozycję, w której się znajdzie. Oto przykład:źródło
W twojej
onCreateView
metodziecontainer
jest tak naprawdęViewPager
instancją.Po prostu dzwonię
zmieni aktualny fragment w twoim
ViewPager
.źródło
vpViewPager.setCurrentItem(1);
. Przejrzałem przykład każdej osoby, nic się nie działo za każdym razem, aż w końcu dotarłem do twojego. Dziękuję Ci.ViewPager
inną stronę, nie zastąpi żadnego fragmentu.Oto moje stosunkowo proste rozwiązanie tego problemu. Kluczami do tego rozwiązania jest użycie
FragmentStatePagerAdapter
zamiast tego,FragmentPagerAdapter
ponieważ pierwszy usunie dla ciebie nieużywane fragmenty, a później zachowa swoje wystąpienia. Drugi to użyciePOSITION_NONE
w getItem (). Użyłem prostej listy do śledzenia moich fragmentów. Moim wymaganiem było zastąpienie całej listy fragmentów jednocześnie nową listą, ale poniższe można łatwo zmodyfikować, aby zastąpić poszczególne fragmenty:źródło
Stworzyłem również rozwiązanie, które działa ze Stacks . Jest to podejście bardziej modułowe, więc nie musisz określać każdego fragmentu i fragmentu szczegółów w swoim
FragmentPagerAdapter
. Jest oparty na przykładzie z ActionbarSherlock, który pochodzi, jeśli jestem bezpośrednio z aplikacji Google Demo.Dodaj to dla funkcji przycisku Wstecz w MainActivity:
Jeśli chcesz zapisać stan fragmentu, gdy zostanie on usunięty. Pozwól swojemu Fragmentowi zaimplementować interfejs
SaveStateBundle
w funkcji jako pakiet ze stanem zapisu. Uzyskaj pakiet po utworzeniu przezthis.getArguments()
.Możesz utworzyć instancję takiej karty:
działa podobnie, jeśli chcesz dodać fragment na stosie kart. Ważne : Myślę, że to nie zadziała, jeśli chcesz mieć 2 wystąpienia tej samej klasy na dwóch kartach. Zrobiłem to rozwiązanie szybko razem, więc mogę je udostępniać tylko bez doświadczenia.
źródło
Zastąpienie fragmentów w przeglądarce jest dość zaangażowane, ale jest bardzo możliwe i może wyglądać super gładko. Po pierwsze, pozwól, aby sam przeglądarka obsługiwała usuwanie i dodawanie fragmentów. Dzieje się tak, gdy zastępujesz fragment wewnątrz SearchFragment, twoja przeglądarka zachowuje swoje widoki fragmentów. Otrzymujesz więc pustą stronę, ponieważ SearchFragment zostaje usunięty przy próbie jej zastąpienia.
Rozwiązaniem jest utworzenie detektora wewnątrz przeglądarki, który będzie obsługiwał zmiany dokonane poza nim, więc najpierw dodaj ten kod do dolnej części adaptera.
Następnie musisz utworzyć prywatną klasę w przeglądarce, która stanie się detektorem, gdy chcesz zmienić swój fragment. Na przykład możesz dodać coś takiego. Zauważ, że implementuje on właśnie utworzony interfejs. Tak więc za każdym razem, gdy wywołasz tę metodę, uruchomi ona kod wewnątrz klasy poniżej.
Należy tu zwrócić uwagę na dwie główne rzeczy:
Zwróć uwagę na detektory umieszczone w konstruktorze „newInstance (listener)”. Oto, jak wywołaszfrfragment0Changed (String newFragmentIdentification) `. Poniższy kod pokazuje, jak utworzyć detektor wewnątrz fragmentu.
static nextFragmentListener listenerSearch;
Możesz nazwać zmianę wewnątrz swojego
onPostExecute
Spowodowałoby to uruchomienie kodu w przeglądarce, aby przełączyć fragment w pozycji zero fragAt0 i stać się nowym searchResultFragment. Są jeszcze dwa małe elementy, które należy dodać do przeglądarki, zanim zacznie ona działać.
Jednym z nich byłaby metoda zastępowania getItem przeglądarki.
Teraz bez tego ostatniego kawałka nadal byłbyś pusty. Niby kulawy, ale jest to niezbędna część viewPager. Musisz zastąpić metodę getItemPosition przeglądarki. Zwykle ta metoda zwróci POSITION_UNCHANGED, która mówi przeglądarce, aby wszystko pozostało takie samo, więc getItem nigdy nie zostanie wywołany w celu umieszczenia nowego fragmentu na stronie. Oto przykład czegoś, co możesz zrobić
Jak powiedziałem, kod bardzo się angażuje, ale w zasadzie musisz stworzyć niestandardowy adapter do swojej sytuacji. Rzeczy, o których wspomniałem, umożliwią zmianę fragmentu. Chyba wszystko zajmie dużo czasu, więc będę cierpliwy, ale wszystko to będzie miało sens. Warto poświęcić trochę czasu, ponieważ może stworzyć naprawdę zgrabną aplikację.
Oto model użytkowy do obsługi przycisku Wstecz. Umieszczasz to w swojej MainActivity
Będziesz musiał utworzyć metodę o nazwie backPressed () wewnątrz FragmentSearchResults, która wywołuje fragment0changed. To w połączeniu z kodem, który pokazałem wcześniej, poradzi sobie z naciśnięciem przycisku Wstecz. Życzę powodzenia z kodem do zmiany przeglądarki. To wymaga dużo pracy i, o ile się przekonałem, nie ma żadnych szybkich adaptacji. Tak jak powiedziałem, w zasadzie tworzysz niestandardowy adapter przeglądarki i pozwalasz mu obsłużyć wszystkie niezbędne zmiany za pomocą detektorów
źródło
To jest mój sposób na osiągnięcie tego.
Przede wszystkim dodaj kartę
Root_fragment
wewnętrznąviewPager
, w której chcesz wdrożyćfragment
zdarzenie kliknięcia przycisku . Przykład;Przede wszystkim
RootTabFragment
należy uwzględnićFragmentLayout
zmiany fragmentów.Następnie w środku
RootTabFragment
onCreateView
zaimplementujfragmentChange
dla swojegoFirstPagerFragment
Następnie zaimplementuj
onClick
zdarzenie dla przycisku wewnątrzFirstPagerFragment
i ponownie dokonaj zmiany fragmentu.Mam nadzieję, że to ci pomoże.
źródło
Znalazłem proste rozwiązanie, które działa dobrze, nawet jeśli chcesz dodać nowe fragmenty na środku lub zastąpić aktualny fragment. W moim rozwiązaniu powinieneś zastąpić
getItemId()
który powinien zwracać unikalny identyfikator dla każdego fragmentu. Domyślnie nie pozycjonuj.Jest to:
Wskazówka: W tym przykładzie
FirstFragment
iSecondFragment
rozszerza klasa abstrakcyjna PageFragment, która ma metodygetPage()
.źródło
Robię coś podobnego do wize, ale w mojej odpowiedzi możesz zmieniać między dwoma fragmentami, kiedy tylko chcesz. A z odpowiedzią na duże problemy mam pewne problemy przy zmianie orientacji ekranu i takie rzeczy. To wygląda jak PagerAdapter:
Listener zaimplementowałem w działaniu kontenera adaptera, aby umieścić go we fragmencie podczas dołączania, jest to działanie:
Następnie we fragmencie umieszczenie detektora po dołączeniu wywołania:
I wreszcie słuchacz:
źródło
Postępowałem zgodnie z odpowiedziami @wize i @mdelolmo i dostałem rozwiązanie. Dzięki Tony. Ale nieco dostroiłem te rozwiązania, aby poprawić zużycie pamięci.
Problemy, które zaobserwowałem:
Zapisują instancję,
Fragment
której zastąpiono. W moim przypadku jest to Fragment, który trzymaMapView
i uważam, że jest kosztowny. Tak więc utrzymujęFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
zamiastFragment
siebie.Oto moja implementacja.
Link do wersji demo tutaj .. https://youtu.be/l_62uhKkLyM
Do celów demonstracyjnych wykorzystano 2 fragmenty
TabReplaceFragment
iDemoTab2Fragment
pozycję 2. We wszystkich innych przypadkach używamDemoTabFragment
instancji.Wyjaśnienie:
Przechodzę
Switch
z działania doDemoCollectionPagerAdapter
. W oparciu o stan tego przełącznika wyświetlimy poprawny fragment. Kiedy kontrola przełącznik zmienia się, dzwonię wSwitchFragListener
„sonSwitchToNextFragment
metody, gdzie mam zmianę wartościpagerAdapterPosChanged
zmiennej doPOSITION_NONE
. Sprawdź więcej o POSITION_NONE . Spowoduje to unieważnienie getItem i mam logikę, aby utworzyć tam odpowiedni fragment. Przepraszamy, jeśli wyjaśnienie jest nieco niechlujne.Jeszcze raz wielkie podziękowania dla @wize i @mdelolmo za oryginalny pomysł.
Mam nadzieję, że to jest pomocne. :)
Daj mi znać, jeśli ta implementacja ma jakieś wady. To będzie bardzo pomocne dla mojego projektu.
źródło
po badaniach znalazłem rozwiązanie z krótkim kodem. najpierw stwórz publiczną instancję na fragmencie i po prostu usuń swój fragment na onSaveInstanceState, jeśli fragment nie jest odtwarzany po zmianie orientacji.
źródło