Ich verwende eine ViewPager
zusammen mit einer FragmentStatePagerAdapter
, um drei verschiedene Fragmente zu hosten:
Wenn ich Fragment1
von der ViewPager
in der FragmentActivity
bekommen möchte.
Was ist das Problem und wie kann ich es beheben?
Die Hauptantwort basiert auf einem Namen, der vom Framework generiert wird. Wenn sich das jemals ändert, wird es nicht mehr funktionieren.
Was ist mit dieser Lösung, die instantiateItem()
und destroyItem()
Ihrer Fragment(State)PagerAdapter
überschreibt:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return ...;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(...);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
Dies scheint für mich zu funktionieren, wenn es um Fragmente geht, die verfügbar sind. Fragmente, die noch nicht instanziiert wurden, geben beim Aufruf von getRegisteredFragment
null zurück. Aber ich habe dies hauptsächlich verwendet, um die aktuelle Fragment
aus der ViewPager
: adapater.getRegisteredFragment(viewPager.getCurrentItem())
zu holen, und dies wird null
nicht zurückgegeben.
Ich kenne keine anderen Nachteile dieser Lösung. Wenn es welche gibt, würde ich gerne wissen.
Um Fragmente aus einem ViewPager herauszuholen, gibt es eine Menge Antworten hier und in anderen verwandten SO - Threads/Blogs. Jeder, den ich gesehen habe, ist gebrochen, und sie scheinen im Allgemeinen in eine der zwei unten aufgeführten Arten zu fallen. Es gibt einige andere gültige Lösungen, wenn Sie nur das aktuelle Fragment greifen möchten, wie this andere Antwort in diesem Thread.
Wenn Sie FragmentPagerAdapter
verwenden, siehe unten. Wenn Sie FragmentStatePagerAdapter
verwenden, sollten Sie this betrachten. Das Erfassen von Indizes, die nicht die aktuellen Indizes in einem FragmentStateAdapter sind, ist aufgrund der Art von Wenn diese vollständig abgerissen werden, sind sie außerhalb der Sichtweite von offScreenLimit-Grenzen.
Falsch: Pflegen Sie Ihre eigene interne Liste von Fragmenten, die hinzugefügt wird, wenn FragmentPagerAdapter.getItem()
aufgerufen wird.
SparseArray
oder Map
getItem
nur aufgerufen wird, wenn eine Seite zum ersten Mal in der ViewPager
gescrollt wird (oder erhalten wird, wenn Ihre ViewPager.setOffscreenPageLimit(x)
> 0 ist), wenn die Hosting-Activity
Fragment
BEENDET ODER NEU GESTARTET WIRD, WIRD DIE INTERNE SpaseArray
GEL&OUML;SCHT, WENN DIE BENUTZERDEFINIERTE FRAGMENTPAGERACTIVITY WIEDERHERGESTELLT WIRD , ABER HINTER DEN KULISSEN WERDEN DIE INTERNEN VIEWPAGERS-FRAGMENTE NEU ERSTELLT, UND getItem
WIRD _/_ NICHT für einen der Indizes aufgerufen, sodass die Möglichkeit, ein Fragment aus dem Index zu erhalten, für immer verloren geht. Sie können dies berücksichtigen, indem Sie diese Fragmentverweise über FragmentManager.getFragment()
und putFragment
ausspeichern und wiederherstellen. Dies beginnt jedoch, IMHO unordentlich zu machen.Falsch: Konstruieren Sie Ihre eigene Tag-ID, die der in FragmentPagerAdapter
unter der Kapuze verwendeten Verwendung entspricht, und verwenden Sie diese Option, um die Seite Fragmente von FragmentManager
abzurufen.
ViewPager
, das sich jederzeit oder für jede Betriebssystemversion ändern kann.Die Methode, die für diese Lösung neu erstellt wird, ist
private static String makeFragmentName(int viewId, long id) {
return "Android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()
Ein ähnlicher Ansatz zu getItem()
oben, aber nicht lebenszyklusbrechend besteht darin, in instantiateItem()
anstelle von getItem()
einzuhaken, da die erstere jedes Mal aufgerufen wird, wenn der Index erstellt/aufgerufen wird. Siehe diese Antwort
FragmentViewPager
Erstellen Sie Ihre eigene FragmentViewPager
-Klasse aus der source der neuesten Unterstützung lib und ändern Sie die intern verwendete Methode, um die Fragment-Tags zu generieren. Sie können es durch das folgende ersetzen. Dies hat den Vorteil, dass Sie wissen, dass die Tag-Erstellung niemals geändert wird und Sie sich nicht auf eine private API/Methode verlassen, was immer gefährlich ist.
/**
* @param containerViewId the ViewPager this adapter is being supplied to
* @param id pass in getItemId(position) as this is whats used internally in this class
* @return the tag used for this pages fragment
*/
public static String makeFragmentName(int containerViewId, long id) {
return "Android:switcher:" + containerViewId + ":" + id;
}
Wenn Sie ein Fragment für einen Index verwenden möchten, rufen Sie einfach etwas wie diese Methode auf (die Sie in die benutzerdefinierte Variable FragmentPagerAdapter
oder eine Unterklasse einfügen können). Wenn Sie sich dessen bewusst sind, ist das Ergebnis möglicherweise null, wenn getItem dies hat Für diese Seite wurde noch nicht aufgerufen, dh sie wurde noch nicht erstellt.
/**
* @return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
* yet OR is a sibling covered by {@link Android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
* the current positions fragment.
*/
public @Nullable Fragment getFragmentForPosition(int position)
{
String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
return fragment;
}
Dies ist eine einfache Lösung und löst die Probleme der beiden anderen Lösungen, die überall im Web zu finden sind
Fügen Sie Ihrem FragmentPagerAdapter die folgenden Methoden hinzu:
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return mFragmentManager.findFragmentByTag(name);
}
private static String makeFragmentName(int viewId, int index) {
return "Android:switcher:" + viewId + ":" + index;
}
getActiveFragment (0) muss funktionieren.
Hier ist die Lösung, die in ViewPager https://Gist.github.com/jacek-marchwicki/d6320ba9a910c514424d implementiert ist. Wenn etwas fehlschlägt, wird ein gutes Absturzprotokoll angezeigt.
Eine weitere einfache Lösung:
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
Ich weiß, das hat ein paar Antworten, aber vielleicht hilft das jemandem. Ich habe eine relativ einfache Lösung verwendet, als ich ein Fragment
von meinem ViewPager
erhalten musste. In Ihrem Activity
oder Fragment
, das ViewPager
enthält, können Sie mit diesem Code alle Fragment
durchlaufen, die es enthält.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
if(viewPagerFragment != null) {
// Do something with your Fragment
// Check viewPagerFragment.isResumed() if you intend on interacting with any views.
}
}
Wenn Sie die Position Ihres Fragment
im ViewPager
kennen, können Sie einfach getItem(knownPosition)
aufrufen.
Wenn Sie die Position Ihres Fragment
in ViewPager
nicht kennen, können Sie Ihre Kinder Fragments
eine Schnittstelle mit einer Methode wie getUniqueId()
implementieren lassen und diese verwenden differenzieren sie. Oder Sie können alle Fragments
durchlaufen und den Klassentyp überprüfen, z. B. if(viewPagerFragment instanceof FragmentClassYouWant)
!!! EDIT !!!
Ich habe festgestellt, dass getItem
nur dann von einem FragmentPagerAdapter
aufgerufen wird, wenn jedes Fragment
zum ersten Mal erstellt werden muss , danach scheint es, dass die Fragments
mit dem FragmentManager
recycelt werden. Auf diese Weise erstellen viele Implementierungen von FragmentPagerAdapter
neue Fragment
s in getItem
. Unter Verwendung der obigen Methode bedeutet dies, dass wir jedes Mal, wenn Fragment
aufgerufen wird, neue getItem
erstellen, wenn wir alle Elemente in FragmentPagerAdapter
durchlaufen. Aus diesem Grund habe ich einen besseren Ansatz gefunden, indem ich FragmentManager
verwende, um stattdessen Fragment
zu erhalten (unter Verwendung der akzeptierten Antwort). Dies ist eine umfassendere Lösung, die für mich gut funktioniert hat.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
String name = makeFragmentName(mViewPager.getId(), i);
Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
// OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
if(viewPagerFragment != null) {
// Do something with your Fragment
if (viewPagerFragment.isResumed()) {
// Interact with any views/data that must be alive
}
else {
// Flag something for update later, when this viewPagerFragment
// returns to onResume
}
}
}
Und Sie werden diese Methode brauchen.
private static String makeFragmentName(int viewId, int position) {
return "Android:switcher:" + viewId + ":" + position;
}
In meinem Fall hat keine der oben genannten Lösungen funktioniert.
Da ich jedoch den Child Fragment Manager in einem Fragment verwende, wurde Folgendes verwendet:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
Dies funktioniert nur, wenn Ihre Fragmente im Manager dem Viewpager-Element entsprechen.
Um aktuelles sichtbares Fragment von ViewPager zu erhalten. Ich verwende diese einfache Aussage und es funktioniert gut.
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
Dies basiert auf der obigen Antwort von Steven. Dies gibt die tatsächliche Instanz des Fragments zurück, das bereits an die übergeordnete Aktivität angehängt ist.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
if (viewPagerFragment instanceof FragmentOne){
FragmentOne oneFragment = (FragmentOne) viewPagerFragment;
if (oneFragment != null){
oneFragment.update(); // your custom method
}
} else if (viewPagerFragment instanceof FragmentTwo){
FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment;
if (twoFragment != null){
twoFragment.update(); // your custom method
}
}
}
}
Hey, ich habe diese Frage hier beantwortet. Grundsätzlich müssen Sie sie überschreiben
public Object instantiateItem (ViewGroup-Container, int-Position)
methode von FragmentStatePagerAdapter.
Sie müssen don't später getItem()
oder eine andere Methode aufrufen, um die Referenz einer Fragment
zu erhalten, die in ViewPager
gehostet ist. Wenn Sie einige Daten in Fragment
aktualisieren möchten, gehen Sie folgendermaßen vor: Aktualisieren Sie ViewPager dynamisch.
Der Schlüssel ist, neue Daten in Adaper
zu setzen und notifyDataSetChanged()
aufzurufen, die wiederum getItemPosition()
aufrufen wird, eine Referenz Ihrer Fragment
übergeben wird und Ihnen die Möglichkeit gibt, diese zu aktualisieren. Bei allen anderen Methoden müssen Sie auf sich selbst oder einen anderen Hack verweisen, was keine gute Lösung ist.
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(xyzData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
Ich behandelte es, indem ich zunächst eine Liste aller Fragmente (List<Fragment> fragments;
) erstellte, die ich verwenden wollte, und sie dann dem Pager hinzufügte, um die Handhabung des aktuell angezeigten Fragments zu erleichtern.
So:
@Override
onCreate(){
//initialise the list of fragments
fragments = new Vector<Fragment>();
//fill up the list with out fragments
fragments.add(Fragment.instantiate(this, MainFragment.class.getName()));
fragments.add(Fragment.instantiate(this, MenuFragment.class.getName()));
fragments.add(Fragment.instantiate(this, StoresFragment.class.getName()));
fragments.add(Fragment.instantiate(this, AboutFragment.class.getName()));
fragments.add(Fragment.instantiate(this, ContactFragment.class.getName()));
//Set up the pager
pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments));
pager.setOffscreenPageLimit(4);
}
so kann man das nennen:
public Fragment getFragment(ViewPager pager){
Fragment theFragment = fragments.get(pager.getCurrentItem());
return theFragment;
}
dann könnte ich es in eine if-Anweisung stecken, die nur dann laufen würde, wenn es auf dem richtigen Fragment wäre
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
aber das ist nur mein hack and slash-ansatz, aber es hat für mich funktioniert. Ich verwende es, wenn mein rückseitiger button gedrückt wird, um relevante Änderungen an meinem aktuell angezeigten Fragment vorzunehmen.
Ich konnte keinen einfachen, sauberen Weg finden, dies zu tun. Das ViewPager-Widget ist jedoch nur eine andere ViewGroup, die Ihre Fragmente hostet. Der ViewPager hat diese Fragmente als unmittelbare Kinder. Sie könnten also einfach über sie iterieren (mithilfe von .getChildCount () und .getChildAt ()) und nachsehen, ob die von Ihnen gesuchte Fragmentinstanz aktuell in den ViewPager geladen ist und einen Verweis darauf erhalten. Z.B. Sie können ein statisches eindeutiges ID-Feld verwenden, um die Fragmente voneinander zu unterscheiden.
Beachten Sie, dass der ViewPager das gesuchte Fragment möglicherweise nicht geladen hat, da es sich um einen virtualisierenden Container wie ListView handelt.
Muss FragmentPagerAdapter
in Ihre ViewPager-Adapterklasse einbinden.
Wenn Sie FragmentStatePagerAdapter
verwenden, können Sie Ihre Fragment
nicht anhand ihrer ID
finden.
public static String makeFragmentName(int viewPagerId, int index) {
return "Android:switcher:" + viewPagerId + ":" + index;
}
So verwenden Sie diese Methode: -
Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag(
AppMethodUtils.makeFragmentName(mViewPager.getId(), i)
);
InterestViewFragment newFragment = (InterestViewFragment) mFragment;
FragmentPagerAdapter ist die Factory der Fragmente. Um ein Fragment anhand seiner Position zu finden, verwenden Sie Folgendes:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"Android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Beispielcode für v4-Support-API.
Die beste Lösung ist die Erweiterung, die wir unter CodePath genannt SmartFragmentStatePagerAdapter erstellt haben. Diesem Leitfaden folgend, wird das Abrufen von Fragmenten und des aktuell ausgewählten Fragments aus einem ViewPager erheblich vereinfacht. Außerdem wird der Speicher der in den Adapter eingebetteten Fragmente besser verwaltet.
Ich habe dies einfach mit einem etwas anderen Ansatz umgesetzt.
Meine benutzerdefinierte FragmentAdapter.getItem-Methode hat nicht neues MyFragment () zurückgegeben, sondern die Instanz von MyFragment, die im FragmentAdapter-Konstruktor erstellt wurde.
In meiner Aktivität habe ich dann das Fragment vom Adapter erhalten, prüfen, ob es instanziiert wird, Fragment benötigt, dann die erforderlichen Methoden anwenden und verwenden.
in TabLayout gibt es mehrere Registerkarten für Fragment. Sie können das Fragment über Tag anhand des Index des Fragments finden.
Für ex. Der Index für Fragment1 ist 0, also übergeben Sie in der findFragmentByTag()
-Methode das Tag für Viewpager.
String tag = "Android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Der einfachste und prägnanteste Weg. Wenn alle Ihre Fragmente in ViewPager
von verschiedenen Klassen sind, können Sie sie abrufen und wie folgt unterscheiden:
public class MyActivity extends Activity
{
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
In Fragment
public int getArgument(){
return mPage;
{
public void update(){
}
In FragmentActivity
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(Fragment f:fragments){
if((f instanceof PageFragment)&&(!f.isDetached())){
PageFragment pf = (PageFragment)f;
if(pf.getArgument()==pager.getCurrentItem())pf.update();
}
}
Erstellen Sie eine Integer-Ressourcen-ID in /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Dann in der PagerAdapter-Funktion getItem:
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = FragmentOne.newInstance();
mViewPager.setTag(R.integer.page1,fragment);
}
else if (position == 1) {
fragment = FragmentTwo.newInstance();
mViewPager.setTag(R.integer.page2,fragment);
} else if (position == 2) {
fragment = FragmentThree.newInstance();
mViewPager.setTag(R.integer.page3,fragment);
}
return fragment;
}
Dann schreiben Sie in Aktivität diese Funktion, um die Fragmentreferenz zu erhalten:
private Fragment getFragmentByPosition(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = (Fragment) mViewPager.getTag(R.integer.page1);
break;
case 1:
fragment = (Fragment) mViewPager.getTag(R.integer.page2);
break;
case 2:
fragment = (Fragment) mViewPager.getTag(R.integer.page3);
break;
}
return fragment;
}
Rufen Sie die Fragmentreferenz ab, indem Sie die obige Funktion aufrufen und sie in Ihr benutzerdefiniertes Fragment umwandeln:
Fragment fragment = getFragmentByPosition(position);
if (fragment != null) {
FragmentOne fragmentOne = (FragmentOne) fragment;
}
Einfache Möglichkeit, Fragmente im Fragmentmanager zu durchlaufen. Suchen Sie den Viewpager mit dem Argument für die Abschnittsposition in public static PlaceholderFragment newInstance (int sectionNumber) .
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}