ViewPager'in bir parçası olarak ListFragment'taki verileri güncelleme


142

Android'de vP uyumluluğu ViewPager kullanıyorum. My FragmentActivity, ViewPager'imdeki farklı sayfalarda farklı şekillerde görüntülenecek bir grup veri içeriyor. Şimdiye kadar aynı ListFragment'ın 3 örneğine sahibim, ancak gelecekte 3 farklı ListFragment örneğine sahip olacağım. ViewPager dikey bir telefon ekranında, listeler yan yana değil.

Şimdi ListFragment üzerindeki bir düğme, tam bir tam sayfa etkinliği başlatır (FragmentActivity aracılığıyla), döndürür ve FragmentActivity verileri değiştirir, kaydeder, sonra ViewPager içindeki tüm görünümleri güncellemeye çalışır. Burada, sıkışıp kaldım.

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

Gördüğünüz gibi, ilk denemem tüm FragmentPagerAdapter üzerinde DataSetChanged bildirmek oldu ve bu bazen verileri güncellemek için gösterdi, ancak diğerleri bir IllegalStateException var: onSaveInstanceState sonra bu eylem gerçekleştirilemez.

İkinci denemem, ListFragment öğemde bir güncelleme işlevini çağırmaya çalışmaktı, ancak getItem'deki getId 0 döndürdü. Denediğim belgelere göre

findFragmentById () veya findFragmentByTag () kullanarak FragmentManager'dan Fragment'a başvuru alma

ama Parçalarımın etiketini veya kimliğini bilmiyorum! ViewPager için bir android: id = "@ + id / viewpager" ve ListFragment düzeninde benim ListView için bir android: id = "@ android: id / list" var, ancak bunların yararlı olduğunu düşünmüyorum.

Peki, nasıl olabilir: a) tüm ViewPager'i tek seferde güvenli bir şekilde güncelleyin (ideal olarak kullanıcıyı daha önce bulunduğu sayfaya geri döndürme) - kullanıcının görünüm değişikliğini görmesi tamamdır. Veya tercihen, b) ListView'i manuel olarak güncellemek için etkilenen her ListFragment'teki bir işlevi çağırın.

Herhangi bir yardım minnetle kabul edilir!

Yanıtlar:


58

Bir Fragman her başlatıldığında etiketi kaydetmeye çalışın.

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

Bu görünüm Tamam ama sınıfımda çalışmıyor. Sizinle çalışıyor mu? Hala nullparçalara ayrılıyorum.
sandalone

1
Benim için çalışıyor, birkaç projede kullandım. FragmentPagerAdapter kaynak kodunu okumaya çalışın ve hata ayıklamak için bazı günlükleri yazdırabilirsiniz.
Faylon

Teşekkürler bile ekranları döndürdükten sonra bile bir cazibe gibi çalışır.
Mehdi Fanai

5
Şunun için çalışır FragmentPagerAdaperama başarısız FragmentStatePagerAdaper( Fragment.getTag()her zaman dönmek null.
Motor Bai'yi

1
Bu çözümü denedim, parçanın verilerini güncellemek için bir yöntem oluşturdum. Ama şimdi parçanın görüşlerini göremiyorum. Verilerin parçalanacağını görebiliyorum, ancak görünümler görünmüyor. Herhangi bir yardım?
Arun Badole

256

Barkside'ın yanıtı, birlikte çalıştığı parçalara etiket koymadığı için çalışıyor, FragmentPagerAdapterancak çalışmıyor .FragmentStatePagerAdapterFragmentManager

İle FragmentStatePagerAdaptergörünüyor onun kullanılarak, tarafından alabilirsiniz instantiateItem(ViewGroup container, int position)çağrı. Konumdaki parçaya referans döndürür position. FragmentStatePagerAdapterSöz konusu parçaya zaten referans varsa , instantiateItemo parçaya sadece referans döndürür ve getItem()tekrar başlatmaya çağırmaz .

Diyelim ki 50 numaralı parçaya bakıyorum ve 49 numaralı parçaya erişmek istiyorum. Yakın olduklarından, # 49'un zaten başlatılması için iyi bir şans var. Yani,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)

5
Ben sadece "a" bir PagerAdapter (PagerAdapter a = pager.getAdapter ();) yaparak basitleştirmek. Hatta MyFragment f49 = (MyFragment) pager.getAdapter (). İnstantiateItem (çağrı cihazı, 49); instantiateItem (), PagerAdapter kaynağından gelir, bu nedenle instantiateItem ()
Dandre Allison

12
... ve herkesin merak etmesi durumunda, ViewPager, yukarıdaki örnekte 49 olan görüntülenen parçanın (ViewPager.getCurrentItem ()) dizinini takip ediyor ... ama şunu söylemeliyim ki ViewPager API'sinin gerçek Fragment'e doğrudan bir başvuru döndürmeyeceğinden İNANILMIYORUM.
19:13

6
İle FragmentStatePagerAdapter, yukarıdaki kodu kullanarak a.instantiateItem(viewPager, viewPager.getCurrentItem());(49 kurtulmak için) @ mblackwell8 tarafından önerildiği gibi mükemmel çalışır.
Cedric Simon

1
Bekle, bu yüzden sadece parçadaki görünümlere bir şeyler yapmak için içerdiği her parçaya ViewPager geçmek zorunda? Şu anda geçerli sayfada onCreate'de yaptığım her şey bir sonraki sayfada gerçekleşiyor. Bu son derece gölgeli bir sızıntı gibi geliyor.
G_V

instantiateItemyapılacak çağrıların startUpdate/ finishUpdateçağrılarla çevrili olması gerektiğini belirtmek önemlidir . ayrıntılar benzer bir soruya
cevabımda

154

Tamam, sanırım kendi sorumla b) isteği yerine getirmenin bir yolunu buldum, böylece başkalarının yararına paylaşacağım. Bir ViewPager içindeki parçaların etiket şeklinde olduğu "android:switcher:VIEWPAGER_ID:INDEX", VIEWPAGER_IDolduğu R.id.viewpagerXML düzeni ve INDEX viewpager içinde pozisyondur. Yani pozisyon biliniyorsa (örneğin 0), şunları yapabilirim 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
         }
      }

Bunun geçerli bir yol olup olmadığı hakkında hiçbir fikrim yok, ancak daha iyi bir şey önerilinceye kadar yapacak.


4
Yanıtınızı ve sorunuzu + 1'lemek için giriş yaptım.
SuitUp

5
Bu resmi belgelerde olmadığından, kullanmazdım. Gelecek bir sürümde etiketi değiştirirlerse ne olur?
Thierry-Dimitri Roy

4
FragmentPagerAdapter makeFragmentNamebunun yerine biraz daha kötü bir yaklaşım için kullanabileceğiniz Etiketi oluşturmak için kullanılan statik bir yöntemle birlikte gelir :HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn

8
@dgmltn, makeFragmentNameolduğu private staticdosyaya erişimi alınamıyor böylece yöntemi.
StenaviN

1
İsa'nın tatlı annesi bu işe yarıyor! Sadece parmağımı çapraz tutacağım ve kullanacağım.
Bogdan Alexandru

35

Bana sorarsanız, aşağıdaki sayfadaki tüm "aktif" parça sayfalarını takip eden ikinci çözüm daha iyidir: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Barkside'ın cevabı benim için çok acayip.

tüm "aktif" parça sayfalarını takip edersiniz. Bu durumda, ViewPager tarafından kullanılan FragmentStatePagerAdapter içindeki parça sayfalarını takip edersiniz.

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

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

"Etkin olmayan" fragman sayfalarına başvuruda bulunmaktan kaçınmak için FragmentStatePagerAdapter'ın destroyItem (...) yöntemini uygulamalıyız:

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

... ve şu anda görünür olan sayfaya erişmeniz gerektiğinde şunu ararsınız:

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

... burada MyAdapter'ın getFragment (int) yöntemi şöyle görünür:

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

"


@Bağlantıdaki ikinci çözüm basit ...! Teşekkürler .. :)
TheFlash

3
Harita yerine SparseArray kullanırsanız daha iyi
GaRRaPeTa


cevabımı bir SparseArray kullanacak şekilde güncelledi, eğer <11'i hedeflerseniz, bunun yerine bir SparseArrayCompat kullanın, çünkü sürüm 11'de güncellenen sınıf
Frank

2
Bu, yaşam döngüsü olaylarını kıracaktır. Bkz. Stackoverflow.com/a/24662049/236743
Dori

13

Tamam, yukarıdaki @barkside yöntemini test ettikten sonra, benim uygulama ile çalışmak için alamadım. Sonra IOSched2012 uygulamasının da kullandığını ve çözümümü bulduğum yer olduğunu hatırladım viewpager. viewpagerKolay erişilebilir bir şekilde saklanmadığı için herhangi bir parça kimliği veya Etiket kullanmaz .

İşte IOSched uygulamaları HomeActivity'nin önemli kısımları . Anahtarda yattığı için yoruma özellikle dikkat edin . :

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

Sizden Ve örneklerini Fragmentsde FragmentPagerAdaptergibi pek:

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

Ayrıca, Fragmentaramalarınızı şu şekilde korumayı unutmayın :

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }

Ryan, Bu, ViewPager'in parça örneklerinizi 'tutmak' ve bunları erişilebilir bir genel değişkene ayarlamak için kullandığı işlevleri geçersiz kıldığınızı mı söylüyor? Neden geri yükleme örneğinde if (mSocialStreamFragment == null) {?
Thomas Clowes

1
bu güzel bir yanıttır ve io12 uygulama ref için +1 - bunun, yaşam döngüsü değişiklikleri nedeniyle getItem()ebeveyn yeniden oluşturulduktan sonra çağrılmamasından dolayı yaşam döngüsü değişikliklerinde çalışıp çalışmayacağından emin değilsiniz. Bkz. Stackoverflow.com/a/24662049/236743 . Örnekte bu kısmen bir parça için korunmaktadır ancak diğer ikisi için korunmamıştır
Dori

2

FragmentPagerAdapterBazı kaynak kodları kopyalayıp değiştirebilir, getTag()yöntem ekleyebilirsiniz

Örneğin

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

sonra uzatın, bu soyut yöntemi geçersiz kılın, Android Grubu değişiminden korkmanıza gerek yok

FragmentPageAdapter Gelecekte kaynak kodu

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


}

1

Ayrıca sorunsuz çalışır:

sayfa parçasının düzeninde bir yerde:

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

parçanın onCreateView () öğesinde:

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

ve varsa Fragment'ı ViewPager'den döndürme yöntemi:

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

1

Alternatif olarak, setPrimaryItemyöntemi şu şekilde geçersiz kılabilirsiniz FragmentPagerAdapter:

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

0

Başka birine yardımcı olması durumunda yaklaşımımı vermek istiyorum:

Bu benim çağrı cihazı adaptörüm:

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

}

Benim faaliyetimde:

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

}

Sonra şu anki parçası almak için ne:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);

1
Bu, aktivite / parça yaşam döngüsü yöntemlerinden sonra kırılacak
Dori

0

Bu benim çözümüm, çünkü sekmelerimi takip etmeme ve zaten yenilememe gerek yok.

    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);
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.