Aktualisieren Sie die Daten in ListFragment als Teil von ViewPager

142

Ich verwende den v4-Kompatibilitäts-ViewPager in Android. Meine FragmentActivity enthält eine Reihe von Daten, die auf verschiedenen Seiten in meinem ViewPager auf unterschiedliche Weise angezeigt werden sollen. Bisher habe ich nur 3 Instanzen desselben ListFragments, aber in Zukunft werde ich 3 Instanzen verschiedener ListFragments haben. Der ViewPager befindet sich auf einem vertikalen Telefonbildschirm. Die Listen werden nicht nebeneinander angezeigt.

Jetzt startet eine Schaltfläche im ListFragment eine separate ganzseitige Aktivität (über die FragmentActivity), die zurückgibt und FragmentActivity die Daten ändert, speichert und dann versucht, alle Ansichten in ihrem ViewPager zu aktualisieren. Hier stecke ich fest.

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

Wie Sie sehen können, bestand mein erster Versuch darin, DataSetChanged für den gesamten FragmentPagerAdapter zu benachrichtigen. Dies zeigte, dass die Daten manchmal aktualisiert wurden. Bei anderen wurde jedoch eine IllegalStateException angezeigt: Diese Aktion kann nach onSaveInstanceState nicht ausgeführt werden.

Bei meinem zweiten Versuch habe ich versucht, eine Aktualisierungsfunktion in meinem ListFragment aufzurufen, aber getId in getItem hat 0 zurückgegeben. Gemäß den Dokumenten, die ich ausprobiert habe

Abrufen eines Verweises auf das Fragment von FragmentManager mithilfe von findFragmentById () oder findFragmentByTag ()

aber ich kenne das Tag oder die ID meiner Fragmente nicht! Ich habe ein android: id = "@ + id / viewpager" für ViewPager und ein android: id = "@ android: id / list" für meine ListView im ListFragment-Layout, aber ich denke nicht, dass diese nützlich sind.

Wie kann ich also entweder: a) den gesamten ViewPager auf einmal sicher aktualisieren (idealerweise den Benutzer auf die Seite zurückbringen, auf der er zuvor war) - es ist in Ordnung, dass der Benutzer die Ansichtsänderung sieht. Oder vorzugsweise b) eine Funktion in jedem betroffenen ListFragment aufrufen, um die ListView manuell zu aktualisieren.

Jede Hilfe wäre dankbar!

Barkside
quelle

Antworten:

58

Versuchen Sie, das Tag jedes Mal aufzuzeichnen, wenn eine Verlobung instanziiert wird.

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

    public MPagerAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
        mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public int getCount() {
        return 10;
    }

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }

    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}
Faylon
quelle
Das sieht in Ordnung aus, funktioniert aber in meiner Klasse nicht. Funktioniert es bei dir? Ich bekomme immer noch nullFragmente.
Sandalone
1
Es funktioniert bei mir, ich habe es in mehreren Projekten verwendet. Sie können versuchen, den Quellcode von FragmentPagerAdapter zu lesen und einige Protokolle zum Debuggen zu drucken.
Faylon
Dank funktioniert auch nach dem Drehen von Bildschirmen wie ein Zauber. Selbst mit dieser Lösung konnte ich den Inhalt von FragmentA mithilfe von MyActivity aus FragmentB ändern.
Mehdi Fanai
5
Es funktioniert für FragmentPagerAdaper, schlägt aber fehl FragmentStatePagerAdaper( Fragment.getTag()immer zurückkehren null.
Motor Bai
1
Ich habe diese Lösung ausprobiert und eine Methode zum Aktualisieren der Fragmentdaten erstellt. Aber jetzt kann ich die Ansichten des Fragments nicht sehen. Ich kann sehen, dass die Daten fragmentiert werden, aber Ansichten sind nicht sichtbar. Irgendeine Hilfe?
Arun Badole
256

Die Antwort von Barkside funktioniert mit FragmentPagerAdapter, funktioniert aber nicht FragmentStatePagerAdapter, da sie keine Tags für Fragmente setzt, an die sie übergeben wird FragmentManager.

Mit FragmentStatePagerAdapterscheint es , können wir erhalten , indem ihre Verwendung instantiateItem(ViewGroup container, int position)Anruf. Es gibt den Verweis auf das Fragment an der Position zurück position. Wenn FragmentStatePagerAdapterbereits ein Verweis auf das betreffende Fragment vorhanden ist, wird instantiateItemnur der Verweis auf dieses Fragment zurückgegeben und nicht aufgerufen getItem(), um es erneut zu instanziieren.

Angenommen, ich betrachte derzeit Fragment Nr. 50 und möchte auf Fragment Nr. 49 zugreifen. Da sie nahe beieinander liegen, besteht eine gute Chance, dass die Nummer 49 bereits instanziiert wird. So,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Pēteris Caune
quelle
5
Ich würde das vereinfachen, indem ich einfach "a" zu einem PagerAdapter mache (PagerAdapter a = pager.getAdapter ();). Oder sogar MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () stammt von PagerAdapter, Sie müssen es also nicht typisieren, um instantiateItem () aufzurufen
Dandre Allison
12
... und nur für den Fall, dass sich jemand wundert, verfolgt der ViewPager den Index des angezeigten Fragments (ViewPager.getCurrentItem ()), das im obigen Beispiel die 49 ist ... aber ich muss sagen, dass ich Ich bin erstaunt, dass die ViewPager-API keinen direkten Verweis auf das eigentliche Fragment zurückgibt.
mblackwell8
6
Mit funktioniert die FragmentStatePagerAdapterVerwendung des obigen Codes mit a.instantiateItem(viewPager, viewPager.getCurrentItem());(um die 49 loszuwerden), wie von @ mblackwell8 vorgeschlagen, perfekt.
Cedric Simon
1
Warten Sie, also muss ich den ViewPager an jedes Fragment übergeben, das er enthält, um die Ansichten im Fragment bearbeiten zu können. Derzeit geschieht alles, was ich in onCreate auf der aktuellen Seite mache, auf der nächsten Seite. Dies klingt in Bezug auf Leckagen extrem schattig.
G_V
Es ist wichtig zu erwähnen, dass alle Anrufe an instantiateItemvon startUpdate/ finishUpdateAnrufe umgeben sein sollten. Details sind in meiner Antwort auf eine ähnliche Frage: stackoverflow.com/questions/14035090/…
Morgwai
154

OK, ich denke, ich habe in meiner eigenen Frage einen Weg gefunden, die Anfrage b) auszuführen, damit ich sie zum Nutzen anderer teilen kann. Das Tag der Fragmente in einem ViewPager befindet sich in der Form "android:switcher:VIEWPAGER_ID:INDEX", in der VIEWPAGER_IDsich das R.id.viewpagerXML-Layout befindet, und INDEX ist die Position im Viewpager. Wenn also die Position bekannt ist (zB 0), kann ich Folgendes ausführen updateFragments():

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

Ich habe keine Ahnung, ob dies eine gültige Methode ist, aber es wird funktionieren, bis etwas Besseres vorgeschlagen wird.

Barkside
quelle
4
Ich habe mich nur angemeldet, um +1 Ihre Antwort und Frage zu erhalten.
SuitUp
5
Da dies nicht in der offiziellen Dokumentation enthalten ist, würde ich es nicht verwenden. Was ist, wenn sie das Tag in einer zukünftigen Version ändern?
Thierry-Dimitri Roy
4
FragmentPagerAdapter enthält eine statische Methode makeFragmentName, mit der das Tag generiert wird, das Sie stattdessen für einen etwas weniger hackigen Ansatz verwenden können: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn
8
@dgmltn makeFragmentNameist eine private staticMethode, sodass Sie keinen Zugriff darauf erhalten können.
StenaviN
1
Süße Mutter Jesu, das funktioniert! Ich drücke nur die Daumen und benutze es.
Bogdan Alexandru
35

Wenn Sie mich fragen, ist die zweite Lösung auf der folgenden Seite, die alle "aktiven" Fragmentseiten im Auge behält, besser: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Die Antwort von Barkside ist mir zu hackig.

Sie verfolgen alle "aktiven" Fragmentseiten. In diesem Fall verfolgen Sie die Fragmentseiten im FragmentStatePagerAdapter, der vom ViewPager verwendet wird.

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

Um zu vermeiden, dass ein Verweis auf "inaktive" Fragmentseiten beibehalten wird, müssen wir die destroyItem (...) -Methode des FragmentStatePagerAdapter implementieren:

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

... und wenn Sie auf die aktuell sichtbare Seite zugreifen müssen, rufen Sie an:

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... wo die getFragment (int) -Methode des MyAdapters folgendermaßen aussieht:

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}

""

Frank
quelle
@Frank zweite Lösung im Link ist einfach ...! Danke .. :)
TheFlash
3
Besser, wenn Sie ein SparseArray anstelle einer Karte verwenden
GaRRaPeTa
oder SparseArrayCompat
Dori
Meine Antwort wurde aktualisiert, sodass ein SparseArray verwendet wird. Wenn Sie auf <11 abzielen, verwenden Sie stattdessen einen SparseArrayCompat, da die Klasse in Version 11 aktualisiert wurde.
Frank
2
Dies wird bei Lebenszyklusereignissen unterbrochen. Siehe stackoverflow.com/a/24662049/236743
Dori
13

Okay, nachdem ich die Methode von @barkside oben getestet hatte, konnte ich sie nicht mit meiner Anwendung zum Laufen bringen. Dann erinnerte ich mich, dass die IOSched2012-App auch eine verwendet viewpager, und dort fand ich meine Lösung. Es werden keine Fragment-IDs oder Tags verwendet, da diese nicht viewpagerauf leicht zugängliche Weise gespeichert werden .

Hier sind die wichtigen Teile der IOSched-Apps HomeActivity. Achten Sie besonders auf den Kommentar , da darin der Schlüssel liegt:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

Und speichern Instanzen von Ihnen Fragmentsin FragmentPagerAdapteretwa so:

    private class HomePagerAdapter extends FragmentPagerAdapter {
    public HomePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

Denken Sie auch daran, Ihre FragmentAnrufe wie folgt zu schützen :

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }
Ryan R.
quelle
Ryan, heißt das, dass Sie die Funktionen überschreiben, die der ViewPager verwendet, um Ihre Fragmentinstanzen zu behalten und sie auf eine öffentliche Variable zu setzen, die zugänglich ist? Warum haben Sie auf der Wiederherstellungsinstanz if (mSocialStreamFragment == null) {?
Thomas Clowes
1
Dies ist eine nette Antwort und +1 für die io12-App. Sie können sich nicht sicher sein, ob dies bei Änderungen des Lebenszyklus funktioniert, obwohl sie getItem()nicht aufgerufen werden, nachdem das übergeordnete Element aufgrund einer Änderung des Lebenszyklus neu erstellt wurde. Siehe stackoverflow.com/a/24662049/236743 . Im Beispiel ist dies teilweise für ein Fragment geschützt, nicht jedoch für die beiden anderen
Dori
2

Sie können FragmentPagerAdaptereinen Quellcode kopieren und ändern und eine getTag()Methode hinzufügen

beispielsweise

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

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

    final long itemId = getItemId(position);


    String name = getTag(position);
    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,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

@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();
    }
}

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

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

Erweitern Sie es dann, überschreiben Sie diese abstrakte Methode und müssen Sie keine Angst vor Änderungen in der Android-Gruppe haben

FragmentPageAdapter Quellcode in der Zukunft

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


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


}
Jiang Qi
quelle
1

Funktioniert auch ohne Probleme:

irgendwo im Layout des Seitenfragments:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

in onCreateView () des Fragments:

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

und Methode zum Zurückgeben von Fragment aus ViewPager, falls vorhanden:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}
Hox
quelle
1

Alternativ können Sie die setPrimaryItemMethode FragmentPagerAdapterwie folgt überschreiben :

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}
Vlad Spreys
quelle
0

Ich möchte meinen Ansatz angeben, falls er jemand anderem helfen kann:

Dies ist mein Pager-Adapter:

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

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

}

In meiner Tätigkeit habe ich:

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

Um das aktuelle Fragment zu erhalten, mache ich Folgendes:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
Kiduxa
quelle
1
Dies wird nach Aktivitäts- / Fragment-Lebenszyklus-Methoden brechen
Dori
0

Dies ist meine Lösung, da ich meine Registerkarten nicht im Auge behalten und sie trotzdem alle aktualisieren muss.

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
abhi08638
quelle