Android фрагмент - Как сохранить состояния представлений во фрагменте, когда другой фрагмент помещается поверх него

137

В FragAандроиде фрагмент (скажем ) добавляется в backstack, а другой фрагмент (скажем FragB) попадает на вершину. Теперь на ответный удар FragAприходит наверх и onCreateView()называется. Теперь я находился FragAв определенном состоянии, прежде чем FragBменя оттолкнули.

Мой вопрос, как я могу восстановить FragAего предыдущее состояние? Есть ли способ сохранить состояние (например, в Bundle) и если да, то какой метод мне следует переопределить?

pankajagarwal
источник

Ответы:

98

В примере фрагмента руководства FragmentList вы можете найти:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Который вы можете использовать позже, как это:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Я новичок во Фрагментах, но это похоже на решение вашей проблемы;) OnActivityCreated вызывается после возврата фрагмента из стека назад.

Ania
источник
18
Я не мог заставить это работать saveInstanceState всегда был нулевым. Я добавляю фрагмент через макет XML. Пришлось изменить mCurCheckPosition на static, тогда он работает, но чувствует себя хакером.
Скоттиаб
57
он не вызывает onSaveInstanceState - с чего бы это? Таким образом, этот подход не работает.
полночь
10
Будет ли этот подход действительно работать, если мы хотим сохранить состояние фрагмента при возврате из другого фрагмента в той же операции? onSaveInstanceState () вызывается только для событий Activity onPause / onStop. Согласно документации: «Также как действие, вы можете сохранить состояние фрагмента, используя Bundle, в случае, если процесс действия прерван, и вам нужно восстановить состояние фрагмента, когда действие воссоздается. Вы можете сохранить состояние во время обратный вызов фрагмента onSaveInstanceState () и его восстановление во время onCreate (), onCreateView () или onActivityCreated (). "
Парамвир Сингх
26
К сведению, этот подход неверен и не должен иметь места, где бы он был близок к голосованию. onSaveInstanceStateвызывается только тогда, когда соответствующая активность также завершается.
Мартин Конечни
16
onSaveInstanceState () вызывается только когда изменения конфигурации происходят, и активность разрушается, этот ответ неверен
Тадас Валайтис
83

Фрагменты onSaveInstanceState(Bundle outState)никогда не будут вызваны, пока активность фрагмента не вызовет его на себе и на прикрепленных фрагментах. Таким образом, этот метод не будет вызываться до тех пор, пока что-то (обычно вращение) не заставит активность SaveInstanceStateи не восстановит ее позже. Но если у вас есть только одно действие и большой набор фрагментов внутри него (с интенсивным использованием replace) и приложение запускается только в одном ориентированном действии, то оно onSaveInstanceState(Bundle outState)может не вызываться в течение длительного времени.

Я знаю три возможных обходных пути.

Первый:

используйте аргументы фрагмента для хранения важных данных:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

Второй, но менее педантичный способ - держать переменные в синглетонах

Третий - не replace()фрагменты, а add()/ show()/ hide()их вместо.

Федор Волчек
источник
3
Лучшее решение, когда Fragment.onSaveInstanceState()никогда не звонили. Просто сохраните ваши собственные данные в аргумент, включая элементы в представлении списка или только их идентификаторы (если у вас есть другой централизованный менеджер данных). Нет необходимости сохранять позицию представления списка - она ​​была сохранена и восстановлена ​​автоматически.
Джон Пан
Я пытался использовать ваш пример в моем приложении, но это: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);всегда null. В чем проблема?
Fragon
Использование фрагмента - getArguments()это ОПРЕДЕЛЕННО путь, включая вложенные фрагменты в ViewPager. Я использую 1 упражнение и меняю местами много фрагментов, и это прекрасно работает. Вот простой тест для проверки любого предложенного решения: 1) перейти от фрагмента A к фрагменту B; 2) изменить ориентацию устройства дважды; 3) нажмите кнопку возврата на устройстве.
Энди Х.
Я попробовал первый подход, но он не работал для меня. getArguments () всегда возвращает ноль. Это также имеет смысл, потому что фрагмент заменяется, и в onCreate () вы устанавливаете новый Bundle, чтобы старый Bundle был утерян. Что я пропустил и был ли я не прав?
Цви
@Zvi, я написал этот код 1,5 года назад и не помню всех деталей, но, как я помню, замена не воссоздает фрагменты, фрагмент воссоздается только в том случае, если вы создали новый экземпляр из своего кода. В этом случае, очевидно, конструктор вызывает и setArguments(new Bundle());перезаписывает старый Bundle. Поэтому убедитесь, что вы создали фрагмент только один раз, а затем используйте этот экземпляр вместо того, чтобы каждый раз создавать новый.
Федор Волчёк
20

Просто обратите внимание, что если вы работаете с фрагментами с помощью ViewPager, это довольно просто. Вам нужно только вызвать этот метод: setOffscreenPageLimit().

Согласовать с документами:

Задайте количество страниц, которые должны быть сохранены по обе стороны от текущей страницы в иерархии представления в состоянии ожидания. Страницы, превышающие этот предел, будут воссозданы из адаптера при необходимости.

Подобная проблема здесь

e_v_e
источник
5
Это другое. setOffScreenPageLimit действует как кэш (имеется в виду, сколько страниц должен обрабатывать ViewPager в данный момент), но не используется для сохранения состояния фрагмента.
Рено Матье
В моем случае это работало с setOffscreenPageLimit () - хотя фрагменты были уничтожены, состояние просмотра было сохранено и восстановлено.
Давинчо
Спасибо, мне тоже помогло.
Илия
Годы спустя, и это все еще так актуально. Хотя это на самом деле не отвечает на вопрос, оно решает проблему
Supreme Dolphin
19

Просто раздуйте свой взгляд на этот раз.

Пример следует:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Lym Zoy
источник
также следует сохранить существующие фрагменты в массиве или что-то
Амир
Я слышал, что хранить ссылку на rootView фрагмента - это плохая практика. Может ли это привести к утечкам?
giraffe.guru
1
@ giraffe.guru Fragmentссылается на свое корневое представление, этого не делать. В то время как ссылки на некоторые элементы GC-корня будут, как и глобальное статическое свойство, не-пользовательской переменной потока. FragmentЭкземпляр не является GC-корень, поэтому он может быть мусора. Так же как и его корневой вид.
LYM Zoy
Ты же мой день.
Вижендра Патидар
Сладко и просто.
Рупам Дас
8

Я работал с проблемой, очень похожей на эту. Так как я знал, что часто буду возвращаться к предыдущему фрагменту, я проверил, был ли фрагмент .isAdded()истинным, и если да, то вместо того, чтобы делать a, transaction.replace()я просто делаю a transaction.show(). Это удерживает фрагмент от воссоздания, если он уже находится в стеке - сохранение состояния не требуется.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Следует также помнить, что, хотя это сохраняет естественный порядок для самих фрагментов, вам все равно может потребоваться обработка самого действия, которое уничтожается и воссоздается при изменении ориентации (конфигурации). Чтобы обойти это в AndroidManifest.xml для вашего узла:

android:configChanges="orientation|screenSize"

В Android 3.0 и выше, по- screenSizeвидимому, требуется.

Удачи

rmirabelle
источник
action.addToBackStack (button_id + "stack_item"); // что делает эта строка. Что такое button_id здесь?
raghu_3
button_id - это просто созданная переменная. Строковый аргумент, передаваемый addToBackStack, является просто необязательным именем для состояния backstack - вы можете установить его в null, если вы управляете только одним backstack.
rmirabelle
У меня возникла проблема, похожая на эту проблему с фрагментами, пожалуйста, посмотрите на это - stackoverflow.com/questions/22468977/…
raghu_3
1
Никогда не добавляйте android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsв свой манифест. Вместо этого узнайте, как сохранить и восстановить состояние, например, отсюда: speakerdeck.com/cyrilmottier/…
Marcin Koziński
А потом, когда вы узнали, насколько безумно громоздким является состояние сохранения и что представленное решение решает проблему наиболее чисто в вашей конкретной ситуации, продолжайте и используйте ее, не беспокоясь о негативных ответах от тех, кто не согласен ;-)
rmirabelle
5

Лучшее решение, которое я нашел ниже:

onSavedInstanceState (): всегда вызывается внутри фрагмента, когда действие прекращается (перемещение действия от одного к другому или изменение конфигурации). Таким образом, если мы вызываем несколько фрагментов для одной и той же деятельности, тогда мы должны использовать следующий подход:

Используйте OnDestroyView () фрагмента и сохраните весь объект внутри этого метода. Тогда OnActivityCreated (): проверьте, является ли объект нулевым или нет (поскольку этот метод вызывается каждый раз). Теперь восстановите состояние объекта здесь.

Его работает всегда!

Аман Гоэль
источник
4

если вы обрабатываете изменения конфигурации в вашей активности фрагмента, указанной в манифесте Android, как это

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

тогда onSaveInstanceStateфрагмент не будет вызван, и savedInstanceStateобъект всегда будет нулевым.

Брук Олдре
источник
1

я не думаю, что onSaveInstanceStateэто хорошее решение. это просто использовать для деятельности, которая была уничтожена.

Начиная с Android 3.0 Fragmen был менеджером FragmentManager, условие: одно действие отображает фрагменты Мэнни, когда фрагмент добавляется (не заменяет: он будет воссоздан) в backStack, представление будет удалено. если вернуться к последнему, он будет отображаться как прежде.

Так что я думаю, что fragmentanger и транзакция достаточно хороши, чтобы справиться с этим.

smallfatter
источник
0

Я использовал гибридный подход для фрагментов, содержащих представление списка. Это кажется производительным, так как я не заменяю текущий фрагмент, а добавляю новый фрагмент и скрываю текущий. У меня есть следующий метод в упражнении, в котором хранятся мои фрагменты:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Я использую этот метод в своем фрагменте (содержащем представление списка) всякий раз, когда на элемент списка нажимают / касаются (и поэтому мне нужно запустить / отобразить фрагмент сведений):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()возвращает массив строк, которые я использую в качестве тегов для разных фрагментов, когда добавляю новый фрагмент (см. transaction.addметод в addFragmentметоде выше).

Во фрагменте, содержащем представление списка, я делаю это с помощью метода onPause ():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Затем в onCreateView фрагмента (фактически в методе, который вызывается в onCreateView), я восстанавливаю состояние:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Джавад Садекзаде
источник
0

В конце концов, после того, как я попробовал многие из этих сложных решений, мне нужно было только сохранить / восстановить одно значение в моем фрагменте (содержимое EditText), и хотя это может быть не самым элегантным решением, создание SharedPreference и сохранение моего состояния там работали на меня

VMMF
источник
0

Простой способ сохранить значения полей в разных фрагментах в деятельности

Создайте экземпляры фрагментов и добавьте вместо замены и удаления

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Затем просто покажите и скройте фрагменты вместо того, чтобы снова добавлять и удалять их

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Срижеш К Наир
источник
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Сатиш Кумар
источник
5
Пожалуйста, рассмотрите возможность включения некоторой информации о вашем ответе, а не просто отправьте код. Мы стараемся не просто исправить ситуацию, но и помочь людям учиться. Вы должны объяснить, что было не так в исходном коде, что вы сделали по-другому и почему ваши изменения сработали.
Эндрю Барбер