андроїдний фрагмент - як зберегти стани поглядів у фрагменті, коли інший фрагмент натискається на нього


137

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

Моє запитання - як я можу відновити FragAйого попередній стан? Чи є спосіб збереження стану (наприклад, у пакеті), і якщо так, то який метод я повинен переосмислити?

Відповіді:


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

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


18
Я не міг змусити це зберегти роботуInstanceState завжди було нульовим. Я додаю фрагмент через макет xml Довелося змінити mCurCheckPosition на статичний, то він працює, але відчуває себе хакі.
scottyab

57
він не закликаєSaveInstanceState - навіщо це? Отже, такий підхід не працює.
опівночі

10
Чи дійсно такий підхід спрацює, якщо ми хочемо зберегти стан фрагмента, повертаючись з іншого фрагмента в тій самій діяльності? onSaveInstanceState () викликається лише на подіях onPause / onStop. Згідно з документацією: "Так само, як і діяльність, ви можете зберегти стан фрагмента, використовуючи пакет, у випадку, якщо процес діяльності буде вбито, і вам потрібно відновити стан фрагмента, коли активність відтворена. Ви можете зберегти стан під час зворотний виклик фрагмента onSaveInstanceState () та відновити його під час onCreate (), onCreateView () або onActivityCreate (). "
Paramvir Singh

26
Для запису цей підхід є неправильним і не повинен мати місця, де немає голосів. onSaveInstanceStateвикликається лише тоді, коли його відповідна діяльність також вимикається.
Мартін Конечний

16
onSaveInstanceState () викликається onle, коли відбулися зміни конфігурації і активність знищується, ця відповідь неправильна
Tadas Valaitis

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) перейдіть від фрагмента А до фрагмента В; 2) змінити орієнтацію пристрою двічі; 3) натисніть кнопку назад на пристрої.
Енді Х.

Я спробував перший підхід, але це не спрацювало. getArguments () завжди повертає null. Це також має сенс, оскільки фрагмент замінюється, і в onCreate () ви встановлюєте новий Bundle, щоб старий пакет втрачався. Що я пропускаю і чи не помиляюсь?
Zvi

@ Zvi, я написав цей код 1,5 року тому і не пам’ятаю всі деталі, але, як я пам’ятаю, заміна не відтворює фрагменти, фрагмент відтворений, лише якщо ви створили новий екземпляр зі свого коду. У цьому випадку, очевидно, конструктор викликав і setArguments(new Bundle());перезаписував старий пакет. Тому переконайтеся, що ви створили фрагмент лише один раз, а потім використовуйте цей примірник, а не створювати новий кожен раз.
Федір Волчйок

20

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

Приєднання до документів:

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

Аналогічне питання тут


5
Це різне. setOffScreenPageLimit діє як кеш (означає, скільки сторінок повинен обробляти ViewPager в даний момент), але не використовується для збереження стану фрагмента.
Рено Матьє

У моєму випадку він працював із setOffscreenPageLimit () - хоча фрагменти були знищені, стан перегляду було збережено та відновлено.
Давінчо

Дякую, мені теж допомогли.
Ілля

Через роки, і це все ще так актуально. Хоча він насправді не відповідає на питання, але вирішує проблему
Верховний Дельфін

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

}


також слід зберегти наявні фрагменти у масиві чи щось таке
Амір

Я чув, що зберігання посилання на фрагмент rootView є поганою практикою - це може призвести до витоків [потрібне цитування]?
giraffe.guru

1
@ giraffe.guru Fragmentпосилається, що його кореневий вигляд цього не зробить. Хоча посилання на деякі GC-кореневі елементи , як глобальна статична властивість, не змінна нитка потоку. FragmentПримірник не є GC-корінь, тому він може бути сміття. Так буде і його кореневий погляд.
Lym Zoy

Ти ж мій день.
Vijendra patidar

Солодке і просте.
Рупам Дас

8

Я працював із питанням, дуже подібним до цього. Оскільки я знав, що часто повертаюсь до попереднього фрагмента, я перевіряв, чи є фрагмент .isAdded()правдивим, і якщо так, то замість того, щоб робити це, transaction.replace()я просто роблю 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очевидно, потрібно.

Удачі


action.addToBackStack (button_id + "stack_item"); // що робить цей рядок. Що тут кнопка_id?
raghu_3

button_id - лише складена змінна. Аргумент рядка, переданий до addToBackStack, є лише необов'язковим іменем для стану заднього заходу - ви можете встановити його на нульове значення, якщо ви керуєте лише однією backstack.
rmirabelle

, Я маю питання схожий на це з фрагментами, будь ласка , ви можете подивитися в IT- stackoverflow.com/questions/22468977 / ...
raghu_3

1
Ніколи не додайте android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsу свій маніфест. Натомість дізнайтеся, як зберегти та відновити стан, наприклад, звідси: speakerdeck.com/cyrilmottier/…
Marcin

А потім, як тільки ви дізнаєтеся, наскільки шалено незграбний стан збереження, і що представлене рішення вирішує проблему найбільш чисто у вашій конкретній ситуації, продовжуйте її та використовуйте, не турбуючись про прихильність тих, хто не погоджується ;-)
rmirabelle

5

Найкраще рішення, яке я знайшов, знаходиться нижче:

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

Використовуйте фрагмент OnDestroyView () і збережіть весь об'єкт всередині цього методу. Тоді OnActivityCreate (): Перевірте, чи об’єкт недійсний чи ні (Тому що цей метод викликає кожен раз). Тепер відновіть стан об’єкта тут.

Це працює завжди!


4

якщо ви обробляєте зміни конфігурації у вашій фрагментарній діяльності, вказаній у маніфесті Android, як це

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

тоді onSaveInstanceStateфрагмент не буде викликаний, і savedInstanceStateоб'єкт завжди буде нульовим.


1

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

З андроїда 3.0 Fragmen керував FragmentManager, умова полягає в тому, що одна активність, яка відображає манні фрагменти, коли фрагмент додається (не замінюється: він буде відтворений) у backStack, вигляд буде знищений. коли повернеться до останнього, він відображатиметься як і раніше.

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


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 і зберігати мій стан там працювали на мене


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
Будь ласка, подумайте, як включити трохи інформації про свою відповідь, а не просто розміщувати код. Ми намагаємося не просто «виправляти», а допомагати людям вчитися. Ви повинні пояснити, що було неправильним у вихідному коді, що ви робили по-іншому, і чому ваші зміни змінювались.
Ендрю Барбер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.