Zaktualizuj dane w ListFragment jako część ViewPager

142

Używam ViewPagera zgodnego z wersją 4 w systemie Android. Moja FragmentActivity zawiera zbiór danych, które mają być wyświetlane na różne sposoby na różnych stronach w moim ViewPager. Do tej pory mam tylko 3 wystąpienia tego samego ListFragment, ale w przyszłości będę miał 3 wystąpienia różnych ListFragmentów. ViewPager znajduje się na pionowym ekranie telefonu, listy nie są obok siebie.

Teraz przycisk na ListFragment uruchamia oddzielne działanie na całej stronie (za pośrednictwem FragmentActivity), które zwraca, a FragmentActivity modyfikuje dane, zapisuje je, a następnie próbuje zaktualizować wszystkie widoki w swoim ViewPager. To tutaj utknąłem.

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;
        }
   ...

Teraz, jak widać, moją pierwszą próbą było powiadomienieDataSetChanged na całym FragmentPagerAdapter, co pokazało, że czasami aktualizuje dane, ale w innych otrzymałem wyjątek IllegalStateException: nie można wykonać tej akcji po onSaveInstanceState.

Moja druga próba wywołała próbę wywołania funkcji aktualizacji w moim ListFragment, ale getId w getItem zwróciło 0. Zgodnie z dokumentacją, którą wypróbowałem

pozyskiwanie odniesienia do fragmentu z FragmentManager za pomocą findFragmentById () lub findFragmentByTag ()

ale nie znam tagu ani identyfikatora moich fragmentów! Mam android: id = "@ + id / viewpager" dla ViewPager i android: id = "@ android: id / list" dla mojego ListView w układzie ListFragment, ale nie sądzę, żeby były przydatne.

W jaki więc sposób mogę: a) bezpiecznie zaktualizować cały ViewPager za jednym razem (najlepiej przywracając użytkownika do strony, na której był wcześniej) - nie ma problemu, aby użytkownik zobaczył zmianę widoku. Lub najlepiej b) Wywołaj funkcję w każdym ListFragment, którego dotyczy problem, aby ręcznie zaktualizować ListView.

Każda pomoc zostanie przyjęta z wdzięcznością!

barkside
źródło

Odpowiedzi:

58

Spróbuj nagrać tag za każdym razem, gdy tworzona jest instancja Fragement.

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
źródło
Wygląda dobrze, ale nie działa w mojej klasie. Czy to działa z tobą? Nadal mam nullza fragment.
sandalone
1
U mnie działa, użyłem go w kilku projektach. Możesz spróbować odczytać kod źródłowy FragmentPagerAdapter i wydrukować niektóre dzienniki do debugowania.
faylon
Dzięki działa jak urok nawet po obróceniu ekranów, nawet udało mi się zmienić zawartość Fragmentu A za pomocą MyActivity z FragmentB, używając tego rozwiązania.
Mehdi Fanai
5
Działa w przypadku, FragmentPagerAdaperale nie działa FragmentStatePagerAdaper( Fragment.getTag()zawsze wracają null.
Engine Bai
1
Wypróbowałem to rozwiązanie, stworzyłem metodę aktualizacji danych fragmentu. Ale teraz nie jestem w stanie zobaczyć widoków fragmentu. Widzę, że dane ulegną fragmentacji, ale widoki nie są widoczne. Jakaś pomoc?
Arun Badole
256

Odpowiedź Barkside działa, FragmentPagerAdapterale nie działa FragmentStatePagerAdapter, ponieważ nie ustawia tagów na fragmentach, do których przekazuje FragmentManager.

Ze FragmentStatePagerAdapterwydaje możemy dostać się za pomocą swojego instantiateItem(ViewGroup container, int position)połączenia. Zwraca odniesienie do fragmentu na pozycji position. Jeśli FragmentStatePagerAdapterjuż zawiera odniesienie do danego fragmentu, instantiateItempo prostu zwraca odniesienie do tego fragmentu i nie wywołuje getItem()ponownego utworzenia jego wystąpienia.

Więc przypuśćmy, że aktualnie patrzę na fragment # 50 i chcę uzyskać dostęp do fragmentu # 49. Ponieważ są blisko, istnieje duża szansa, że ​​wystąpienie # 49 zostanie już utworzone. Więc,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Pēteris Caune
źródło
5
Uprościłbym to, tworząc „a” jako PagerAdapter (PagerAdapter a = pager.getAdapter ();). Lub nawet MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () jest z PagerAdapter, więc nie trzeba go typecast zadzwonić instantiateItem ()
Dandre Allison
12
... i na wypadek gdyby ktoś się zastanawiał, ViewPager śledzi indeks oglądanego fragmentu (ViewPager.getCurrentItem ()), który jest 49 w powyższym przykładzie ... ale muszę powiedzieć, że ja Jestem ZASKOCZONY, że ViewPager API nie zwróci bezpośrednio odniesienia do właściwego fragmentu.
mblackwell8
6
Z FragmentStatePagerAdapter, użycie powyższego kodu z a.instantiateItem(viewPager, viewPager.getCurrentItem());(aby pozbyć się 49) zgodnie z propozycją @ mblackwell8 działa idealnie.
Cedric Simon,
1
Czekaj, więc muszę przekazać ViewPager do każdego zawartego w nim fragmentu, aby móc zrobić coś z widokami w tym fragmencie? Obecnie wszystko, co robię w onCreate na bieżącej stronie, dzieje się na następnej stronie. Brzmi to wyjątkowo podejrzanie, jeśli chodzi o wyciek.
G_V,
ważne jest, aby wspomnieć, że wszelkie wywołania do instantiateItempowinny być otoczone przez startUpdate/ finishUpdatecall. szczegóły są w mojej odpowiedzi na podobne pytanie: stackoverflow.com/questions/14035090/…
morgwai
154

OK, myślę, że znalazłem sposób na wykonanie prośby b) w moim własnym pytaniu, więc podzielę się nią z korzyścią dla innych. Znacznik fragmentów wewnątrz ViewPager ma postać "android:switcher:VIEWPAGER_ID:INDEX", gdzie VIEWPAGER_IDjest R.id.viewpagerukładem XML, a INDEX to pozycja w viewpager. Więc jeśli pozycja jest znana (np. 0), mogę wykonać w 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
         }
      }

Nie mam pojęcia, czy to właściwy sposób, ale wystarczy, dopóki nie zaproponuje się czegoś lepszego.

barkside
źródło
4
Zalogowałem się tylko po to, aby dać +1 Twojej odpowiedzi i pytaniu.
SuitUp
5
ponieważ nie ma tego w oficjalnej dokumentacji, nie użyłbym tego. A jeśli zmienią tag w przyszłej wersji?
Thierry-Dimitri Roy
4
FragmentPagerAdapter zawiera statyczną metodę makeFragmentNameużywaną do generowania tagu, którego można użyć zamiast nieco mniej HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
hakerskiego
8
@dgmltn makeFragmentNamejest private staticmetodą, więc nie możesz uzyskać do niej dostępu.
StenaviN
1
Słodka Matko Jezusa, to działa! Po prostu trzymam kciuk i go używam.
Bogdan Alexandru
35

Jeśli o mnie chodzi, drugie rozwiązanie na poniższej stronie, śledzące wszystkie „aktywne” strony z fragmentami, jest lepsze: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Odpowiedź z barkside jest dla mnie zbyt hakerska.

śledzisz wszystkie „aktywne” strony fragmentów. W takim przypadku możesz śledzić strony fragmentów w FragmentStatePagerAdapter, który jest używany przez ViewPager.

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

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

Aby uniknąć utrzymywania odniesienia do „nieaktywnych” stron fragmentów, musimy zaimplementować metodę zniszczItem (...) FragmentStatePagerAdapter:

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

... a kiedy potrzebujesz uzyskać dostęp do aktualnie widocznej strony, zadzwoń:

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

... gdzie metoda getFragment (int) MyAdapter wygląda następująco:

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

"

Szczery
źródło
@Frank Drugie rozwiązanie w linku jest proste ...! Dzięki .. :)
TheFlash
3
Lepiej, jeśli używasz SparseArray zamiast mapy
GaRRaPeTa
zaktualizowałem moją odpowiedź, więc używa SparseArray, jeśli cel <11, użyj zamiast tego SparseArrayCompat, ponieważ klasa zaktualizowana w wersji 11.
Frank
2
Spowoduje to przerwanie w przypadku zdarzeń w cyklu życia. Zobacz stackoverflow.com/a/24662049/236743
Dori,
13

W porządku, po przetestowaniu metody przy użyciu @barkside powyżej, nie mogłem zmusić jej do pracy z moją aplikacją. Potem przypomniałem sobie, że aplikacja IOSched2012 również używa a viewpageri tam właśnie znalazłem swoje rozwiązanie. Nie używa żadnych identyfikatorów fragmentów ani tagów, ponieważ nie są one przechowywane viewpagerw łatwo dostępny sposób.

Oto ważne części z aplikacji IOSched HomeActivity. Zwróć szczególną uwagę na komentarz , bo w nim tkwi klucz .:

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

I przechowuj swoje instancje Fragmentsw następujący FragmentPagerAdaptersposób:

    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;
    }

Pamiętaj też, aby pilnować swoich Fragmentpołączeń w ten sposób:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }
Ryan R.
źródło
Ryan, czy to znaczy, że przesłoniłeś funkcje, których ViewPager używa do „zachowania” twoich instancji fragmentów i ustawiasz je na publiczną zmienną, która jest dostępna? Dlaczego w przypadku przywracania instancji masz if (mSocialStreamFragment == null) {?
Thomas Clowes
1
to dobra odpowiedź i +1 dla odniesienia do aplikacji io12 - nie jestem pewien, czy to zadziała przy zmianach cyklu życia, ale z powodu getItem()braku wywołania po odtworzeniu rodzica z powodu zmiany cyklu życia. Zobacz stackoverflow.com/a/24662049/236743 . W tym przykładzie jest to częściowo chronione przez jeden fragment, ale nie przez dwa pozostałe
Dori,
2

Możesz skopiować FragmentPagerAdapteri zmodyfikować kod źródłowy, dodać getTag()metodę

na przykład

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

następnie rozszerz go, zastąp te abstrakcyjne metody, nie musisz się bać zmiany grupy Androida

FragmentPageAdapter kod źródłowy w przyszłości

 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
źródło
1

Działa również bez problemów:

gdzieś w układzie fragmentu strony:

<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>

we fragmencie onCreateView ():

...
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;

i metoda zwracania fragmentu z ViewPager, jeśli istnieje:

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
źródło
1

Alternatywnie możesz nadpisać setPrimaryItemmetodę z FragmentPagerAdapterponiższego:

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
źródło
0

Chcę przedstawić swoje podejście na wypadek, gdyby mogło pomóc komukolwiek innemu:

Oto mój adapter do pagera:

 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;
    }

}

W swojej działalności mam:

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);
          ...
         }

}

Następnie, aby uzyskać bieżący fragment, robię:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
kiduxa
źródło
1
to się zepsuje po metodach cyklu życia działania / fragmentu
Dori
0

To jest moje rozwiązanie, ponieważ nie muszę śledzić moich kart i i tak muszę je wszystkie odświeżyć.

    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
źródło