Perbarui data di ListFragment sebagai bagian dari ViewPager

142

Saya menggunakan ViewPager kompatibilitas v4 di Android. FragmentActivity saya memiliki banyak data yang akan ditampilkan dengan cara berbeda pada halaman berbeda di ViewPager saya. Sejauh ini saya hanya memiliki 3 instance dari ListFragment yang sama, tetapi di masa depan saya akan memiliki 3 instance ListFragments yang berbeda. ViewPager berada di layar ponsel vertikal, daftar tidak berdampingan.

Sekarang tombol pada ListFragment memulai aktivitas satu halaman terpisah (melalui FragmentActivity), yang mengembalikan dan FragmentActivity memodifikasi data, menyimpannya, lalu mencoba memperbarui semua tampilan dalam ViewPager-nya. Di sinilah saya terjebak.

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

Sekarang seperti yang Anda lihat, upaya pertama saya adalah memberi tahuDataSetChanged pada seluruh FragmentPagerAdapter, dan ini menunjukkan untuk memperbarui data kadang-kadang, tetapi orang lain saya mendapat IllegalStateException: Tidak dapat melakukan tindakan ini setelah onSaveInstanceState.

Upaya kedua saya melibatkan mencoba untuk memanggil fungsi pembaruan di ListFragment saya, tetapi getId di getItem kembali 0. Sesuai dokumen yang saya coba dengan

memperoleh referensi ke Fragmen dari FragmentManager, menggunakan findFragmentById () atau findFragmentByTag ()

tapi saya tidak tahu tag atau id dari Fragmen saya! Saya memiliki android: id = "@ + id / viewpager" untuk ViewPager, dan android: id = "@ android: id / list" untuk ListView saya di tata letak ListFragment, tapi saya rasa ini tidak berguna.

Jadi, bagaimana saya bisa: a) memperbarui seluruh ViewPager dengan aman dalam sekali jalan (idealnya mengembalikan pengguna ke halaman sebelumnya) - tidak apa-apa jika pengguna melihat perubahan tampilan. Atau lebih disukai, b) memanggil fungsi di setiap ListFragment yang terpengaruh untuk memperbarui ListView secara manual.

Bantuan apa pun akan diterima dengan penuh syukur!

barkside
sumber

Jawaban:

58

Cobalah untuk merekam tag setiap kali FrMENT dipakai.

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
sumber
Ini terlihat OK tapi tidak berfungsi di kelas saya. Apakah itu berhasil dengan Anda? Saya masih mendapatkan nullfragmen.
sandalone
1
Ini bekerja untuk saya, saya menggunakannya di beberapa proyek. Anda dapat mencoba membaca kode sumber dari FragmentPagerAdapter dan mencetak beberapa log untuk debug.
faylon
Terima kasih berfungsi seperti pesona bahkan setelah memutar layar. Bahkan saya dapat mengubah konten FragmentA menggunakan MyActivity dari FragmentB, menggunakan solusi ini.
Mehdi Fanai
5
Ini bekerja untuk FragmentPagerAdapertetapi gagal untuk FragmentStatePagerAdaper( Fragment.getTag()selalu kembali null.
Mesin Bai
1
Saya mencoba solusi ini, menciptakan metode untuk memperbarui data fragmen. Tapi sekarang saya tidak bisa melihat pandangan fragmen. Saya bisa melihat data akan terpecah-pecah tetapi tampilan tidak terlihat. Ada bantuan?
Arun Badole
256

Jawaban Barkside berfungsi dengan FragmentPagerAdaptertetapi tidak bekerja FragmentStatePagerAdapter, karena tidak menetapkan tag pada fragmen yang diteruskannya FragmentManager.

Dengan FragmentStatePagerAdapteritu sepertinya kita bisa bertahan, menggunakan instantiateItem(ViewGroup container, int position)panggilannya. Ini mengembalikan referensi ke fragmen di posisi position. Jika FragmentStatePagerAdaptersudah memegang referensi untuk fragmen yang dimaksud, instantiateItemcukup kembalikan referensi ke fragmen itu, dan jangan panggil getItem()untuk membuat instance lagi.

Jadi, misalkan, saya sedang melihat fragmen # 50, dan ingin mengakses fragmen # 49. Karena mereka dekat, ada kemungkinan besar # 49 akan sudah dipakai. Begitu,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Pēteris Caune
sumber
5
Saya akan menyederhanakannya dengan hanya membuat "a" a PagerAdapter (PagerAdapter a = pager.getAdapter ();). Atau bahkan MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () berasal dari PagerAdapter, jadi Anda tidak perlu mengetikkannya untuk memanggil instantiateItem ()
Dandre Allison
12
... dan kalau-kalau ada yang bertanya-tanya, ViewPager melacak indeks fragmen yang dilihat (ViewPager.getCurrentItem ()), yang merupakan 49 dalam contoh di atas ... tapi saya harus mengatakan bahwa saya Sedang AMAZED bahwa API ViewPager tidak akan langsung mengembalikan referensi ke Fragmen yang sebenarnya.
mblackwell8
6
Dengan FragmentStatePagerAdapter, menggunakan kode di atas dengan a.instantiateItem(viewPager, viewPager.getCurrentItem());(untuk menghilangkan 49) seperti yang diusulkan oleh @ mblackwell8 bekerja dengan sempurna.
Cedric Simon
1
Tunggu, jadi saya harus meneruskan ViewPager ke setiap fragmen yang dikandungnya hanya untuk dapat melakukan sesuatu terhadap tampilan dalam fragmen? Saat ini semua yang saya lakukan di onCreate di halaman saat ini terjadi di halaman berikutnya. Ini kedengarannya sangat bocor.
G_V
Penting untuk menyebutkan bahwa panggilan apa pun instantiateItemharus dikelilingi oleh startUpdate/ finishUpdatepanggilan. detail ada di jawaban saya untuk pertanyaan serupa: stackoverflow.com/questions/14035090/...
morgwai
154

OK, saya pikir saya telah menemukan cara untuk melakukan permintaan b) dalam pertanyaan saya sendiri jadi saya akan berbagi untuk kepentingan orang lain. Tag fragmen di dalam ViewPager ada di formulir "android:switcher:VIEWPAGER_ID:INDEX", di mana VIEWPAGER_IDada R.id.viewpagerdi tata letak XML, dan INDEX adalah posisi di viewpager. Jadi, jika posisinya diketahui (misalnya 0), saya dapat tampil di 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
         }
      }

Saya tidak tahu apakah ini cara yang valid untuk melakukannya, tetapi itu akan dilakukan sampai sesuatu yang lebih baik disarankan.

barkside
sumber
4
Saya masuk hanya untuk memberi +1 pada jawaban dan pertanyaan Anda.
SuitUp
5
karena ini tidak ada dalam dokumentasi resmi, saya tidak akan menggunakannya. Bagaimana jika mereka mengubah tag di rilis mendatang?
Thierry-Dimitri Roy
4
FragmentPagerAdapter hadir dengan metode statis yang makeFragmentNamedigunakan untuk menghasilkan Tag yang bisa Anda gunakan sebagai gantinya untuk pendekatan yang sedikit kurang- HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
hacky
8
@dgmltn, makeFragmentNameadalah private staticmetode, jadi Anda tidak bisa mendapatkan akses ke sana.
StenaviN
1
Ibu Yesus yang manis ini berhasil! Saya hanya akan tetap menyilangkan jari saya dan menggunakannya.
Bogdan Alexandru
35

Jika Anda bertanya kepada saya, solusi kedua pada halaman di bawah ini, melacak semua halaman fragmen "aktif", lebih baik: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Jawaban dari barkside terlalu membingungkan bagi saya.

Anda melacak semua halaman fragmen "aktif". Dalam hal ini, Anda melacak halaman fragmen di FragmentStatePagerAdapter, yang digunakan oleh ViewPager.

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

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

Untuk menghindari menyimpan referensi ke halaman fragmen "tidak aktif", kita perlu menerapkan metode destrItem (...) dari FragmentStatePagerAdapter:

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

... dan ketika Anda perlu mengakses halaman yang terlihat saat ini, Anda kemudian memanggil:

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

... di mana metode getFragment (int) MyAdapter terlihat seperti ini:

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

"

jujur
sumber
@Jujur solusi kedua dalam tautan ini sederhana ...! Terima kasih .. :)
TheFlash
3
Lebih baik jika Anda menggunakan SparseArray alih-alih peta
GaRRaPeTa
atau SparseArrayCompat
Dori
memperbarui jawaban saya sehingga menggunakan SparseArray, jika Anda menargetkan <11, gunakan SparseArrayCompat sebagai gantinya karena kelas apa yang diperbarui dalam versi 11.
Frank
2
Ini akan merusak acara siklus hidup. Lihat stackoverflow.com/a/24662049/236743
Dori
13

Oke, setelah menguji metode dengan @barkside di atas, saya tidak bisa membuatnya berfungsi dengan aplikasi saya. Lalu saya ingat bahwa aplikasi IOSched2012 menggunakan viewpagerjuga, dan di situlah saya menemukan solusi saya. Itu tidak menggunakan ID fragmen atau Tag karena ini tidak disimpan dengan viewpagercara yang mudah diakses.

Inilah bagian -bagian penting dari aplikasi IOSched HomeActivity. Berikan perhatian khusus pada komentar , karena di situlah letak kunci .:

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

Dan simpan contoh Anda Fragmentsdi FragmentPagerAdapterseperti ini:

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

Juga, ingatlah untuk menjaga Fragmentpanggilan Anda seperti:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }
Ryan R
sumber
Ryan, Apakah ini untuk mengatakan bahwa Anda mengungguli fungsi yang digunakan ViewPager untuk 'menyimpan' contoh fragmen Anda dan mengaturnya ke variabel publik yang dapat diakses? Mengapa pada instance pemulihan yang Anda miliki jika (mSocialStreamFragment == null) {?
Thomas Clowes
1
ini adalah jawaban yang bagus dan +1 untuk ref io12 aplikasi - tidak yakin apakah ini akan bekerja di seluruh perubahan siklus hidup meskipun karena getItem()tidak dipanggil setelah induk dibuat kembali karena perubahan siklus hidup. Lihat stackoverflow.com/a/24662049/236743 . Dalam contoh ini sebagian dilindungi untuk satu fragmen tetapi tidak untuk dua fragmen yang lain
Dori
2

Anda dapat menyalin FragmentPagerAdapterdan memodifikasi beberapa kode sumber, menambahkan getTag()metode

sebagai contoh

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

kemudian perluas, timpa metode abstrak ini, tidak perlu takut dengan perubahan Grup Android

FragmentPageAdapter kode sumber di masa depan

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

Juga berfungsi tanpa masalah:

di suatu tempat di tata letak fragmen halaman:

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

di onCreateView fragmen ():

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

dan metode untuk mengembalikan Fragmen dari ViewPager, jika ada:

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

Atau Anda dapat mengganti setPrimaryItemmetode dari FragmentPagerAdapterseperti:

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

Saya ingin memberikan pendekatan saya jika itu dapat membantu orang lain:

Ini adaptor pager saya:

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

}

Dalam aktivitas saya, saya memiliki:

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

}

Kemudian untuk mendapatkan fragmen saat ini yang saya lakukan adalah:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
kiduxa
sumber
1
ini akan berhenti setelah metode siklus aktivitas / fragmen
Dori
0

Ini solusi saya karena saya tidak perlu melacak tab saya dan perlu menyegarkan semuanya.

    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
sumber