ViewPager ve parçalar - parçanın durumunu saklamanın doğru yolu nedir?


492

UI mantığının bazı modüllere ayrılması için parçalar çok hoş görünüyor. Ancak ViewPageryaşam döngüsü ile birlikte bana hala puslu. Bu yüzden Guru düşüncelerine çok ihtiyaç var!

Düzenle

Aşağıdaki aptal çözüme bakın ;-)

Dürbün

Ana faaliyetin bir ViewPagerfragmanları vardır. Bu fragmanlar diğer (alt alan) aktiviteler için biraz farklı bir mantık uygulayabilir, böylece fragmanların verileri aktivite içindeki bir geri arama arayüzü aracılığıyla doldurulur. Ve ilk lansmanda her şey iyi çalışıyor, ama! ...

Sorun

Etkinlik yeniden oluşturulduğunda (örn. Yönelim değişikliği gibi) ViewPagerparçaları da öyle. Kod (aşağıda bulacaksınız), etkinlik her oluşturulduğunda ViewPager, parçalarla aynı yeni bir parça bağdaştırıcısı oluşturmaya çalıştığımı söylüyor (belki de sorun budur), ancak FragmentManager zaten tüm bu parçaların bir yerde (nerede?) Saklandığını ve bunlar için rekreasyon mekanizmasını başlatır. Bu nedenle, rekreasyon mekanizması, aktivitenin uygulanmış yöntemi aracılığıyla veri başlatmak için geri arama arabirim çağrısıyla "eski" parçanın onAttach, onCreateView, vb. Ancak bu yöntem, Etkinliğin onCreate yöntemiyle oluşturulan yeni oluşturulan parçayı gösterir.

Konu

Belki de yanlış desenler kullanıyorum, ancak Android 3 Pro kitabında bile bu konuda çok fazla şey yok. Yani, lütfen bana bir-iki yumruk vermek ve bunu doğru şekilde nasıl yapılacağını işaret etmektedir. Çok teşekkürler!

kod

Ana aktivite

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity aka yardımcı

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

adaptör

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

fragman

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Çözüm

Aptal çözüm, putFragment ile (Host Aktivitesinin) onSaveInstanceState içindeki parçaları kaydetmek ve getFragment aracılığıyla onCreate içinde onları almaktır. Ama hala işlerin böyle çalışmaması gerektiği garip bir his var ... Aşağıdaki koda bakınız:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

1
Şimdi merak ediyorum: OnCreate () 'de kullanmak için onSavedInstancestate üzerinden çok farklı bir yaklaşım kullanmalı mıyım veya ana faaliyetin parçalarını (Dashboard) kaydetmeye çalışmalıyım. Bu parçaları kaydetmenin ve onCreate'da paketten almanın uygun bir yolu var mı? Parcelable gibi görünmüyor ...
Oleksii Malovanyi

2
2. yaklaşım işe yarıyor - bkz. Ama çirkin bir kod parçası gibi görünüyor, değil mi?
Oleksii Malovanyi

1
Android etiketini temizleme çabası için (ayrıntılar: meta.stackexchange.com/questions/100529/… ), çözümünüzü bir yanıt olarak göndermeyi ve seçilen olarak işaretlemeyi düşünür müsünüz ? Bu şekilde cevaplanmamış bir soru olarak görünmeyecek :)
Alexander Lucas

1
evet, bence sorun yok.
Benimden

1
Aptal çözüm bile çalışıyor mu? Bana boş gösterici istisnası veriyor ..
Zhen Liu

Yanıtlar:


451

Ne zaman FragmentPagerAdapterFragmentManager bir parçasını ekler, bu parça alınacaktır söz konusu pozisyona dayalı özel bir etiket kullanır. FragmentPagerAdapter.getItem(int position)yalnızca bu konum için bir parça olmadığında çağrılır. Döndükten sonra Android, bu belirli konum için zaten bir parça oluşturduğunu / kaydettiğini fark edecek ve böylece FragmentManager.findFragmentByTag()yeni bir tane oluşturmak yerine onunla yeniden bağlanmaya çalışacak . Bunları kullanırken tüm bunlar ücretsiz olarak gelir FragmentPagerAdapterve parçanın başlatma kodunu getItem(int)yöntemin içinde bulundurmak normaldir .

A kullanmasak bile FragmentPagerAdapter, her seferinde yeni bir parça oluşturmak iyi bir fikir değildir Activity.onCreate(Bundle). Fark ettiğiniz gibi, FragmentManager'a bir parça eklendiğinde, döndürüldükten sonra sizin için yeniden oluşturulacak ve tekrar eklemenize gerek yoktur. Bunu yapmak, parçalarla çalışırken sık karşılaşılan hata nedenidir.

Parçalarla çalışırken olağan bir yaklaşım şudur:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

A kullanırken FragmentPagerAdapter, parça yönetimini adaptöre bırakıyoruz ve yukarıdaki adımları uygulamak zorunda değiliz. Varsayılan olarak, geçerli konumun önüne ve arkasına yalnızca bir Parçayı önceden yükler (siz kullanmadıkça onları yok etmez FragmentStatePagerAdapter). Bu, ViewPager.setOffscreenPageLimit (int) tarafından denetlenir . Bu nedenle, bağdaştırıcının dışındaki parçalarda doğrudan çağrı yöntemlerinin geçerli olduğu garanti edilmez, çünkü canlı bile olmayabilirler.

Uzun bir hikayeyi kısaltmak için, putFragmentdaha sonra referans almak için kullanmak için çözümünüz o kadar çılgın değildir ve yine de parçaları (normalde) kullanmanın normal yolunun aksine değildir. Aksi halde referans almak zordur çünkü parça kişisel olarak değil adaptör tarafından eklenir. Sadece, offscreenPageLimitmevcut parçalara güvendiğinizden, istediğiniz parçaları yüklemek için yeterince yüksek olduğundan emin olun . Bu, ViewPager'in tembel yükleme yeteneklerini atlar, ancak uygulamanız için istediğiniz gibi görünür.

Başka bir yaklaşım, FragmentPageAdapter.instantiateItem(View, int)geri çağırmadan önce süper çağrıdan döndürülen parçaya bir referansı geçersiz kılmak ve kaydetmek (zaten varsa parçayı bulmak için mantığı vardır).

Daha dolgun bir resim için FragmentPagerAdapter (short) ve ViewPager (long) kaynaklarına bir göz atın .


7
Son kısmı sevdim. Parçaları için bir önbellek vardı ve içinde önbellek mantığı koymak taşındı FragmentPageAdapter.instantiateItem(View, int). Sonunda sadece döndürme / yapılandırma değişikliğinde görünen ve beni çılgına
çeviren

2
Bu, rotasyon değişikliği konusunda yaşadığım bir sorunu düzeltti. Belki bakmak için göremiyorum ama bu belgeleniyor mu? önceki bir durumdan kurtarmak için etiketi mi kullanıyorsunuz? Belki açık bir cevap olabilir, ancak Android geliştirme konusunda oldukça yeniyim.

1
@ Endüstriyel antidepresan Parçanın ekleneceği kabın (örn. Bir FrameLayout) kimliğidir.
antonyt

1
btw, benim için FragmentPageAdapter.instantiateItem(ViewGroup, int)değil FragmentPageAdapter.instantiateItem(View, int).
Görünen Ad

1
Btw, parça ekrandan kaydırıldığında hangi (varsa) parça yaşam döngüsü yönteminin çağrıldığını biliyor musunuz? Yoksa onDetach()başka bir şey mi?
ygesher

36

Bir çözüm sunmak istiyoruz bu konuda genişlediğinde antonytbireyin harika cevap ve geçersiz kılma denince FragmentPageAdapter.instantiateItem(View, int)oluşturulan başvurular kaydetmek için Fragmentsdaha sonra üzerlerinde çalışma yapmak, böylece. Bu aynı zamanda FragmentStatePagerAdapter; ayrıntılar için notlara bakınız.


Aşağıda, Fragmentstarafından FragmentPagerAdapterverilen iç tagskümeye bağlı olmayan döndürülen bir referansın nasıl alınacağına dair basit bir örnek verilmiştir Fragments. Anahtar instantiateItem()referansları geçersiz kılmak ve oraya kaydetmek yerine içinde getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

ya datags sınıf üyesi değişkenleri / referansları yerine çalışmayı tercih Fragmentsederseniz, tagsseti FragmentPagerAdapteraynı şekilde yakalayabilirsiniz : NOT: bu,FragmentStatePagerAdaptertags oluştururken ayarlanmadığı için geçerli değildir Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Bu yöntemin dahili taklidi taklit etmediğine dikkat edin. tag kümeyiFragmentPagerAdapter ve bunları almak için uygun API'leri kullandığını unutmayın. Bu şekilde tag, gelecekteki sürümlerindeki değişiklikler bile SupportLibrarygüvende olacaksınız.


Unutma , aramalarınızdan tasarımına bağlı olduğunu Activity, Fragmentssen olabilir çalışma çalışıyorsanız veya sahip böyle yaparak bunun için hesaba, henüz olmayabilir nullreferanslarını kullanmadan önce çekleri.

Ayrıca, bunun yerine üzerinde çalışıyorsanız FragmentStatePagerAdapter, o zaman Fragmentsçok sayıda referansınız olduğu için sert referanslar tutmak istemezsiniz ve zor referanslar gereksiz yere hafızada tutar. Bunun yerine, Fragmentreferansları WeakReferencestandart yerine değişkenlere kaydedin . Bunun gibi:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

bellek yetersizliği android FragmentManager'dan kullanılmayan parçaları yok etmesini isteyebilir, değil mi? Haklıysam, ikinci sürümünüz işe yarayacak, ilk sürüm başarısız olabilir. (Şu anki android sürümlerinin bunu yapıp yapmadığını bilmiyorum, ancak sanki bunu beklememiz gerekiyor gibi görünüyor.)
Moritz Her ikisi de

1
FragmetPagerAdapterher ekran döndürmede bir inCreate Etkinlik oluşturmaya ne dersiniz . Bu yanlış mı, çünkü daha önce eklenen parçaları yeniden kullanamayabilirFragmentPagerAdapter
Bahman

Geçersiz kılma instantiateItem()yolu; bu, ekran döndürme işlemlerini gerçekleştirmemde ve Etkinlik ve Bağdaştırıcı yeniden başlatıldıktan sonra mevcut Parça örneklerimi almama yardımcı oldu; Kendimi bir hatırlatma olarak kodda yorum bıraktı: Döndürme sonra, getItem()çağrılmaz; sadece bu yöntem instantiateItem()çağrılır. Süper uygulama instantiateItem(), yeni örneklerin örneklendirilmesi yerine, rotasyondan sonra (gerekirse) parçaları yeniden bağlar!
HelloImKevo

Fragment createdFragment = (Fragment) super.instantiateItem..İlk çözümde boş gösterici alıyorum .
AlexS

Bu sorunun çeşitli yinelenen / varyant tekrarlarını araştırdıktan sonra, bu en iyi çözümdü. (Daha alakalı bir başlık ile başka bir Q'dan çapraz referans verildi, bu yüzden bunun için teşekkür ederim!)
MandisaW

18

Sorunuz için nispeten kolay bir çözüm buldum.

FragmentPagerAdapter kaynak kodundan görebileceğiniz gibi , FragmentPagerAdaptermağaza tarafından yönetilen parçalar aşağıdakiler FragmentManagerkullanılarak oluşturulan etiketin altında yönetilir :

String tag="android:switcher:" + viewId + ":" + index;

viewIdOlduğunu container.getId(), containersizin ise ViewPagerörneği. indexFragmanının konumudur. Böylece nesne kimliğini aşağıdakilere kaydedebilirsiniz outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

Bu parça ile iletişim kurmak istiyorsanız FragmentManager, aşağıdaki gibi alabilirsiniz :

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

21
hmmm Bu hiçbir şekilde sonsuza kadar aynı kalacak bir iç etiketi adlandırma kuralları cevap gibi gitmek için iyi bir yol olduğunu sanmıyorum.
Dori

1
İçine güvenmeyen bir çözüm arayanlar için tag, cevabımı denemeyi düşünün .
Tony Chan

16

Cevaplar için yaptığım aramaların çoğu beni bu konuya yönlendirdiğinden, belki biraz farklı bir durum için alternatif bir çözüm sunmak istiyorum.

Benim olayım - Sayfaları dinamik olarak oluşturuyorum / ekliyorum ve bunları bir ViewPager'e kaydırıyorum, ancak döndürüldüğünde (onConfigurationChange) yeni bir sayfa oluşturuyorum çünkü tabii ki OnCreate tekrar çağrılıyor. Ancak, rotasyondan önce oluşturulan tüm sayfalara referans vermek istiyorum.

Sorun - Oluşturduğum her parça için benzersiz tanımlayıcılarım yok, bu yüzden başvurmanın tek yolu referansları bir şekilde Döndürme / yapılandırma değişikliğinden sonra geri yüklenecek bir Dizide saklamaktı.

Çözüm - Anahtar kavram, Etkinliğin (Parçacıkları görüntüleyen) mevcut Parçacıklara yönelik başvuru dizisini de yönetmesini sağlamaktı, çünkü bu etkinlik onSaveInstanceState içindeki Paketleri kullanabilir

public class MainActivity extends FragmentActivity

Bu etkinlik dahilinde, açık sayfaları takip etmesi için özel bir üye beyan ederim

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Bu onSaveInstanceState her çağrıldığında ve onCreate'de geri yüklendiğinde güncellenir

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... böylece depolandıktan sonra geri alınabilir ...

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

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Bunlar ana aktivitede gerekli değişikliklerdi ve bu yüzden çalışması için FragmentPagerAdapter içindeki üyelere ve yöntemlere ihtiyacım vardı.

public class ViewPagerAdapter extends FragmentPagerAdapter

özdeş bir yapı (yukarıda MainActivity'de gösterildiği gibi)

private List<Fragment> _pages = new ArrayList<Fragment>();

ve bu senkronizasyon (onSaveInstanceState'te yukarıda kullanıldığı gibi) özellikle yöntemler tarafından desteklenir

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

Ve son olarak, parça sınıfında

public class CustomFragment extends Fragment

tüm bunların çalışması için önce iki değişiklik vardı

public class CustomFragment extends Fragment implements Serializable

ve sonra bunu Parçalar'ın yok edilmemesi için onCreate'a ekleyin

setRetainInstance(true);

Hâlâ kafamı Fragments ve Android yaşam döngüsüne sarma sürecindeyim, bu nedenle burada dikkat edilmesi gereken, bu yöntemde fazlalıklar / verimsizlikler olabilir. Ama bu benim için çalışıyor ve umarım benimkine benzer davaları olan başkaları için yararlı olabilir.


2
SetRetainInstance için +1 (true) - aradığım kara büyü!
declension

FragmentStatePagerAdapter (v13) kullanarak daha da kolaydı. Devlet geri yükleme ve bırakma ile hangi anlaşma.
Kasap

Açık açıklama ve çok güzel bir çözüm. Teşekkür ederim!
Bruno Bieri

8

Benim çözümüm çok kaba ama işe yarıyor: benim parçaları dinamik olarak tutulan verilerden oluşturulan olmak, ben sadece PageAdapteraramadan önce tüm parçası kaldırmak super.onSaveInstanceState()ve sonra aktivite oluşturma onları yeniden:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

Bunları kaldıramazsınız onDestroy(), aksi takdirde bu istisnayı elde edersiniz:

java.lang.IllegalStateException: Bu eylemden sonra gerçekleştirilemiyor onSaveInstanceState

Sayfa bağdaştırıcısındaki kod:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

Sadece geçerli sayfayı kaydedip onCreate()parçaları oluşturulduktan sonra geri yüklerim.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

Neden bilmiyorum, ama notDataDataSetChanged (); uygulamanın çökmesine neden oluyor
Malachiasz

4

Bu nedir BasePagerAdapter? Standart çağrı cihazı adaptörleri birini kullanmalıdır - Ya FragmentPagerAdapteryaFragmentStatePagerAdapter , artık ihtiyaç duyulmayan Parçaların ViewPagersaklanmasına (eski) veya durumlarının kaydedilmesine (ikincisine) sahip olup olmamasına bağlı olarak tekrar gerekli.

Kullanım için örnek kod ViewPagerbulunabilir burada

Etkinlik örnekleri arasında bir görünüm çağrı cihazında parçaların yönetiminin biraz karmaşık olduğu doğrudur, çünkü FragmentManagerçerçeve içinde durumu kaydetmeye ve çağrı cihazının yaptığı aktif parçaları geri yüklemeye özen gösterir. Tüm bunlar, adaptör başlatıldığında, geri yüklenen parçalarla yeniden bağlandığından emin olması gerektiği anlamına gelir. Bunun koduna bakabilir FragmentPagerAdapterveya FragmentStatePagerAdapterbunun nasıl yapıldığını görebilirsiniz.


1
Benim sorum BasePagerAdapter kodu kullanılabilir. Görüldüğü gibi FragmentPagerAdapter öğesini TitleProvider uygulaması için genişletir . Yani her şey zaten android dev önerilen yaklaşım ile çalışıyor.
Oleksii Malovanyi

"FragmentStatePagerAdapter" nin farkında değildim. Beni tam anlamıyla saatlerce kurtardın. Teşekkürler.
Knossos

2

Herkes FragmentStatePagerAdapter parçalarının durumunu düzgün geri yüklemiyor ile ilgili sorunlar yaşıyorsa ... yani ... yeni FragmentStatePagerAdapter tarafından onları devlet geri yükleme yerine oluşturuluyor ...

Aramadan ViewPager.setOffscreenPageLimit()ÖNCE aradığınızdan emin olunViewPager.setAdapter(fragmentStatePagerAdapter)

Arama ViewPager.setOffscreenPageLimit()yapıldığında ... ViewPager hemen adaptörüne bakacak ve parçalarını almaya çalışacaktır. Bu, ViewPager'ın Fragment'ları savedInstanceState'ten geri yükleme şansına sahip olmadan önce gerçekleşebilir (böylece yeni olduklarından, RegistInstanceState öğesinden yeniden başlatılamayan yeni Fragmanlar oluşturulur).


0

Bu basit ve zarif çözümü buldum. Aktivitenin, Fragmanları oluşturmaktan sorumlu olduğunu ve Adaptörün sadece onlara hizmet ettiğini varsayar.

Bu, bağdaştırıcının kodudur ( mFragmentsEtkinlik tarafından tutulan parçaların bir listesi olması dışında burada garip bir şey yoktur )

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

Bu iş parçacığının tüm sorun "eski" parçalarının bir referans almak, bu yüzden Activity onCreate bu kodu kullanın.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

Elbette gerekirse bu kodun daha da ince ayarını yapabilirsiniz, örneğin parçaların belirli bir sınıfın örnekleri olduğundan emin olabilirsiniz.


0

Oryantasyon değişikliğinden sonra parçaları almak için .getTag () yöntemini kullanmanız gerekir.

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

Biraz daha fazla işlem için, herhangi bir konumda viewPagerId ve FragmentClass tarafından parça almak için PageAdapter için kendi ArrayList yazdı:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

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


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

Bu yüzden sadece parçaları içeren bir MyPageArrayList oluşturun:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

ve bunları viewPager'e ekleyin:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

Bundan sonra oryantasyondan sonra sınıfını kullanarak doğru parçayı değiştirebilirsiniz:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

-30

Ekle:

   @SuppressLint("ValidFragment")

dersinizden önce.

işe yaramazsa böyle bir şey yapmayın:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
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.