ViewPager та фрагменти - який правильний спосіб зберігати стан фрагмента?


492

Фрагменти здаються дуже приємними для поділу логіки інтерфейсу користувача на деякі модулі. Але поряд зі ViewPagerсвоїм життєвим циклом мені все ще туманно. Тож думки Гуру дуже потрібні!

Редагувати

Дивіться дурне рішення нижче ;-)

Область застосування

Основна діяльність має ViewPagerфрагменти. Ці фрагменти могли реалізувати дещо іншу логіку для інших (підрозділів) діяльності, тому дані фрагментів заповнюються через інтерфейс зворотного виклику всередині діяльності. І все працює добре при першому запуску, але! ...

Проблема

Коли активність буде відтворена (наприклад, про зміну орієнтації), зробіть її ViewPagerфрагменти. Код (ви знайдете нижче) говорить про те, що кожного разу, коли діяльність створюється, я намагаюся створити новий ViewPagerадаптер фрагментів, такий самий, як фрагменти (можливо, це проблема), але у FragmentManager усі ці фрагменти вже десь зберігаються (де?) І запускає механізм відпочинку для тих. Тож механізм відпочинку викликає "старий" фрагмент onAttach, onCreateView і т.д. за допомогою мого виклику інтерфейсу зворотного виклику для ініціювання даних за допомогою реалізованого методу Activity. Але цей метод вказує на щойно створений фрагмент, який створюється за допомогою методу onCreate Activity.

Проблема

Можливо, я використовую неправильні зразки, але навіть у програмі Android 3 Pro не дуже багато про це. Тож, будь ласка , дай мені один-два удари та вкажи, як це зробити правильно. Дуже дякую!

Код

Основна діяльність

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 helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Перехідник

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
}

}

Фрагмент

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... */
}

Рішення

Німим рішенням є збереження фрагментів всередині onSaveInstanceState (діяльності хоста) за допомогою putFragment та отримання їх всередині onCreate через getFragment. Але я все ще дивно відчуваю, що все не повинно працювати так ... Дивіться код нижче:

    @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
Мені цікаво зараз: чи варто використовувати зовсім інший підхід чи намагатися зберегти фрагменти основної діяльності (Інформаційна панель) через onSavedInstancestate, щоб використовувати їх у onCreate (). Чи є правильний спосіб зберегти ці фрагменти та дістати їх із пакету в onCreate? Вони, здається, не розчленовані ...
Олексій Мальований

2
2-й підхід працює - див. "Сульгування". Але це, здається, потворний фрагмент коду, чи не так?
Олексій Мальований

1
Заради зусиль по очищенню тегу Android (детальніше тут: meta.stackexchange.com/questions/100529/… ), ви б не хотіли розмістити своє рішення як відповідь та позначити його як вибране? Таким чином він не відображатиметься як питання без відповіді :)
Олександр Лукас

1
так, думаю, що це нормально. Сподівався на щось краще, ніж моє ...
Олексій Мальований

1
Дурне рішення навіть спрацьовує? Це дає мені виключення з нульовим покажчиком ..
Чжен Лю

Відповіді:


451

Коли FragmentPagerAdapterдодає фрагмент до FragmentManager, він використовує спеціальний тег, заснований на конкретній позиції, в якій буде розміщений фрагмент. FragmentPagerAdapter.getItem(int position)називається лише тоді, коли фрагмента для цієї позиції не існує. Після обертання Android помітить, що він вже створив / зберег фрагмент для цієї конкретної позиції, і тому він просто намагається знову з'єднатися з ним FragmentManager.findFragmentByTag(), а не створювати нову. Все це стає безкоштовним при використанні методу FragmentPagerAdapterі тому зазвичай у вашому getItem(int)методі є код ініціалізації фрагмента .

Навіть якщо ми не використовували A FragmentPagerAdapter, це не гарна ідея створювати новий фрагмент кожного разу в Activity.onCreate(Bundle). Як ви вже помітили, коли фрагмент буде доданий до фрагмента управління, він буде відтворений для вас після обертання, і немає необхідності додавати його знову. Це - поширена причина помилок при роботі з фрагментами.

Звичайний підхід при роботі з фрагментами такий:

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 FragmentPagerAdapterми відмовляємося від управління фрагментами до адаптера, і не потрібно виконувати вищезазначені дії. За замовчуванням він буде передзавантажений лише один Фрагмент спереду та позаду поточного положення (хоча він не знищує їх, якщо ви не використовуєте FragmentStatePagerAdapter). Це контролюється ViewPager.setOffscreenPageLimit (int) . Через це, прямі виклики методів на фрагментах за межами адаптера не гарантовано є дійсними, оскільки вони можуть навіть не бути живими.

Якщо скоротити довгу історію, ваше рішення використовувати putFragmentдля того, щоб згодом отримати довідку, не настільки шалене і не на відміну від звичайного способу використання фрагментів у будь-якому випадку (вище). Інакше складно отримати посилання, оскільки фрагмент додається адаптером, а не ви особисто. Просто переконайтеся, що offscreenPageLimitстовпчик достатньо високий для завантаження бажаних фрагментів, оскільки ви покладаєтесь на його наявність. Це обходить ледачі можливості завантаження ViewPager, але, здається, саме те, що ви прагнете у своїй програмі.

Інший підхід - переосмислити FragmentPageAdapter.instantiateItem(View, int)та зберегти посилання на фрагмент, повернутий із супервиклику, перед тим, як повернути його (він має логіку знайти фрагмент, якщо він вже є).

Для більш повної картини ознайомтеся з деяким джерелом FragmentPagerAdapter (короткий) та ViewPager (довгий).


7
Полюбила останню частину. Мав кеш для фрагментів і перемістив покладене в логіку кеша всередині FragmentPageAdapter.instantiateItem(View, int). Нарешті виправлено довготривалу помилку, яка з’являється лише під час зміни обертання / конфігурації та зводить мене з розуму ...
Карлос Собриньо

2
Це вирішило проблему, яка виникала у мене при зміні обертання. Можливо, я просто не бачу, щоб шукати, але це документально підтверджено? тобто використовуючи тег для відновлення з попереднього стану? Це може бути явна відповідь, але я досить новачок у розробці Android.

1
@ Промисловий антидепресант Це ідентифікатор контейнера (наприклад, FrameLayout), до якого слід додати фрагмент.
антоніт

1
btw, для мене це було FragmentPageAdapter.instantiateItem(ViewGroup, int)швидше, ніж FragmentPageAdapter.instantiateItem(View, int).
Відображувати ім’я

1
До речі, ви знаєте, який (якщо такий є) метод життєвого циклу фрагмента викликається, коли фрагмент буде зсунуто з екрана? Це onDetach()чи щось інше?
Єгешер

36

Я хочу запропонувати рішення, яке розширюється і далі antonyt «S чудову відповідь і згадка перевизначення , FragmentPageAdapter.instantiateItem(View, int)щоб зберегти посилання на створений Fragmentsтаким чином Ви можете зробити роботу на них пізніше. З цим також слід працювати FragmentStatePagerAdapter; див. примітки для деталей.


Ось простий приклад того, як отримати посилання на Fragmentsповернене, FragmentPagerAdapterщо не покладається на внутрішній tagsнабір наFragments . Ключовим моментом є переосмислення instantiateItem()та збереження посилань там, а не в 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();
        }
    }
}

або якщо ви віддаєте перевагу працювати з tagsзаміною змінних / посилань члена класу наFragments ви можете також захопити tagsнабір FragmentPagerAdapterтаким же чином: ПРИМІТКА: це не стосується, FragmentStatePagerAdapterоскільки він не встановлюється tagsпри створенні його 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;
}

Зауважте, що цей метод НЕ покладається на наслідування внутрішнього tagнабору, FragmentPagerAdapterа замість цього використовує належні API для їх отримання. Таким чином, навіть якщо tagзміни в майбутніх версіях SupportLibraryви все одно будете в безпеці.


Не забувайте, що залежно від дизайну вашого проекту Activity, Fragmentsви намагаєтеся працювати над тим, чи може ще не існувати, тому вам доведеться це враховувати, роблячи nullперевірки, перш ніж використовувати свої посилання.

Крім того, якщо замість цього ви працюєте FragmentStatePagerAdapter, ви не хочете зберігати жорсткі посилання на свої, Fragmentsтому що у вас їх може бути багато, а жорсткі посилання зайво зберігатимуть їх у пам'яті. Замість цього збережіть Fragmentпосилання у WeakReferenceзмінних замість стандартних. Подобається це:

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

на дефіциті пам'яті андроїд може попросити FragmentManager знищити невикористані фрагменти, правда? Якщо я маю рацію, ваша друга версія запрацює, перша може вийти з ладу. (Не знаю, чи роблять це поточні версії для Android, але, здається, ми повинні цього очікувати.)
Moritz Обидва

1
як щодо створення FragmetPagerAdapterinCreat of Activity у кожному повороті екрана. Це неправильно, оскільки це може обійти повторне використання вже доданих фрагментів уFragmentPagerAdapter
Бахман,

Переоцінка instantiateItem()- це шлях; це допомогло мені обробити обертання екрана та отримати мої наявні екземпляри фрагмента після відновлення діяльності та адаптера; Я залишив собі коментарі в коді як нагадування: після обертання getItem()НЕ викликається; тільки цей метод instantiateItem()викликається. Суперреалізація instantiateItem()насправді знову приєднує фрагменти після обертання (за необхідності), замість того, щоб створювати нові екземпляри!
HelloImKevo

Я отримую нульовий покажчик Fragment createdFragment = (Fragment) super.instantiateItem..у першому рішенні.
AlexS

Після полювання за всіма повторними / варіантними ітераціями цієї проблеми це було найкращим рішенням. (Перехресне посилання з іншого Q із більш релевантною назвою, тож дякую за це!)
MandisaW

18

Я знайшов ще одне порівняно просте рішення для вашого питання.

Як видно з вихідного коду FragmentPagerAdapter , фрагменти, якими керується FragmentPagerAdapterзберігання в FragmentManagerтезі, створеному за допомогою:

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

viewIdЦе container.getId(), то containerваш ViewPagerекземпляр. Положення indexфрагмента. Отже, ви можете зберегти ідентифікатор об'єкта в 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);

Якщо ви хочете поспілкуватися з цим фрагментом, ви можете отримати його FragmentManager, наприклад:

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

21
Хммм, я не думаю, що це хороший спосіб перейти до вашої відповіді на внутрішній тег іменування конвенції, який ні в якому разі не залишиться таким же назавжди.
Дорі

1
Для тих, хто шукає рішення, яке не покладається на внутрішнє tag, подумайте про те , щоб спробувати мою відповідь .
Тоні Чан

16

Я хочу запропонувати альтернативне рішення, можливо, трохи іншого випадку, оскільки багато моїх пошуків відповідей продовжували вести мене до цієї теми.

Моя справа - я динамічно створюю / додаю сторінки та пересуваю їх у ViewPager, але при повороті (onConfigurationChange) я закінчую новою сторінкою, тому що, звичайно, OnCreate знову викликається. Але я хочу зберегти посилання на всі сторінки, які були створені до ротації.

Проблема - у мене немає унікальних ідентифікаторів для кожного створеного фрагмента, тому єдиний спосіб посилання полягав у тому, щоб якось зберігати посилання в масиві, який слід відновити після зміни обертання / конфігурації.

Обхід - Основна концепція полягала в тому, щоб діяльність (яка відображає фрагменти) також управляла масивом посилань на існуючі фрагменти, оскільки ця діяльність може використовувати пачки в OnSaveInstanceState

public class MainActivity extends FragmentActivity

Тож у рамках цієї діяльності я оголошую приватного члена для відстеження відкритих сторінок

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

Це оновлюється кожного разу, коли виклик onSaveInstanceState та відновлення в onCreate

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

... тож після його зберігання його можна буде отримати ...

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

Це були необхідні зміни в основній діяльності, і тому мені потрібні були члени та методи в моєму FragmentPagerAdapter, щоб це працювало, так що в межах

public class ViewPagerAdapter extends FragmentPagerAdapter

ідентична конструкція (як показано вище в MainActivity)

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

і ця синхронізація (як це було використано вище в OnSaveInstanceState) підтримується спеціально методами

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

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

І тоді, нарешті, у класі фрагментів

public class CustomFragment extends Fragment

щоб все це спрацювало, спочатку було дві зміни

public class CustomFragment extends Fragment implements Serializable

а потім додайте це в onCreate, щоб фрагменти не були знищені

setRetainInstance(true);

Я все ще перебуваю в процесі обгортання фрагментів та життєвого циклу Android, тому тут може бути застереження, що в цьому методі можуть бути надмірності / неефективність. Але це працює для мене, і я сподіваюся, що це може бути корисним для інших у випадках, схожих на мої.


2
+1 для setRetainInstance (true) - чорна магія, яку я шукав!
відхилення

Це було ще простіше за допомогою FragmentStatePagerAdapter (v13). Яка угода для відновлення та звільнення держави.
Різник

Чіткий опис і дуже приємне рішення. Дякую!
Бруно Бієрі

8

Моє рішення дуже грубе, але працює: будучи моїми фрагментами, що динамічно створюються із збережених даних, я просто видаляю весь фрагмент із PageAdapterраніше виклику, super.onSaveInstanceState()а потім відтворюю їх під час створення діяльності:

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

Ви не можете їх видалити onDestroy(), інакше ви отримаєте цей виняток:

java.lang.IllegalStateException: Неможливо виконати цю дію після onSaveInstanceState

Ось код у адаптері сторінки:

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

Я зберігаю лише поточну сторінку і відновлюю її onCreate()після створення фрагментів.

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

Я не знаю чому, але сповіститиDataSetChanged (); викликає збій програми
Малахіаш

4

Що це BasePagerAdapter? Вам слід скористатися одним із стандартних адаптерів пейджера - або, FragmentPagerAdapterабо FragmentStatePagerAdapterзалежно від того, чи хочете Ви, щоб фрагменти, які вже не потрібні, ViewPagerабо зберігатись навколо (перший), або зберегти їх стан (останній) та відновити, якщо знову потрібні.

Зразок коду для використання ViewPagerможна знайти тут

Це правда, що керування фрагментами в пейджері огляду через екземпляри діяльності є дещо складним, тому що FragmentManagerв рамках роботи береться за збереження стану та відновлення будь-яких активних фрагментів, які створив пейджер. Все це насправді означає, що адаптер при ініціалізації повинен переконатися, що він знову з'єднується з реставрованими фрагментами. Ви можете подивитися код для FragmentPagerAdapterабо FragmentStatePagerAdapterпобачити, як це робиться.


1
Код BasePagerAdapter доступний у моєму запитанні. Як видно, він просто розширює FragmentPagerAdapter з метою реалізації TitleProvider . Отже, все вже працює з запропонованим підходом для Android-розробників.
Олексій Мальований

Мені не було відомо про "FragmentStatePagerAdapter". Ти врятував мене буквально за години. Дякую.
Кноссос

2

Якщо у когось виникають проблеми зі своїм FragmentStatePagerAdapter, не належним чином відновлюється стан його фрагментів ... тобто ... FragmentStatePagerAdapter створює нові фрагменти замість того, щоб відновити їх із стану ...

Переконайтесь, що ви дзвоните до ViewPager.setOffscreenPageLimit()того, як дзвонітьViewPager.setAdapter(fragmentStatePagerAdapter)

Після дзвінка ViewPager.setOffscreenPageLimit()... ViewPager негайно звернеться до свого адаптера і спробує отримати його фрагменти. Це може статися до того, як у ViewPager з'явиться шанс відновити фрагменти з збереженогоInstanceState (таким чином створюються нові фрагменти, які неможливо повторно ініціалізувати з SavedInstanceState, оскільки вони нові).


0

Я придумав це просте і елегантне рішення. Він передбачає, що діяльність відповідає за створення фрагментів, а адаптер просто їх обслуговує.

Це код адаптера (тут нічого дивного, за винятком того, що mFragmentsце список фрагментів, що підтримується Діяльністю)

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

Вся проблема цього потоку полягає в отриманні посилання на "старі" фрагменти, тому я використовую цей код у onCreate діяльності.

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

Звичайно, ви можете додатково налагодити цей код, якщо потрібно, наприклад, переконавшись, що фрагменти є примірниками певного класу.


0

Щоб отримати фрагменти після зміни орієнтації, ви повинні використовувати .getTag ().

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

Для трохи більшої обробки я написав власний ArrayList для свого PageAdapter, щоб отримати фрагмент by viewPagerId та FragmentClass на будь-якій позиції:

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

Тому просто створіть MyPageArrayList з фрагментами:

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

і додайте їх у viewPager:

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

після цього ви можете після орієнтації змінити правильний фрагмент, використовуючи його клас:

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

-30

додати:

   @SuppressLint("ValidFragment")

перед уроком.

це не працює щось подібне:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.