Как сохранить состояние фрагмента в приложении

Как сохранить состояние фрагмента, когда он отображается в FragmentTabHost?

Благодаря этому tutorial, я могу реализовать FragmentTabHost в своем приложении.

Я намерен создать приложение, основное действие которого содержит несколько вкладок (которые остаются сверху по всему приложению). Щелчок по каждой вкладке открывает новый фрагмент под вкладками.

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

Поток:

введите здесь описание изображения

Мне действительно нужно реализовать эту логику. Если мой подход неверен, пожалуйста, предложите альтернативу.

Спасибо

Код:

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

public class FagTabHostMain extends FragmentActivity {
    FragmentTabHost mTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fag_tab_host_main);

        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

        mTabHost.addTab(mTabHost.newTabSpec("audio").setIndicator("Audio"),
                AudioContainerFragmentClass.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("video").setIndicator("Video"),
                VideoContainerFragmentClass.class, null);

    }

    @Override
    public void onBackPressed() {
        boolean isPopFragment = false;
        String currentTabTag = mTabHost.getCurrentTabTag();
        if (currentTabTag.equals("audio")) {
            isPopFragment = ((AudioContainerFragmentClass) getSupportFragmentManager()
                    .findFragmentByTag("audio")).popFragment();
        } else if (currentTabTag.equals("video")) {
            isPopFragment = ((VideoContainerFragmentClass) getSupportFragmentManager()
                    .findFragmentByTag("video")).popFragment();
        }
        // Finish when no more fragments to show in back stack
        finish();
    }
}

План основной деятельности

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <android.support.v4.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- Not Using this one right now -->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dip"
            android:layout_height="0dip"
            android:layout_weight="0" />

    </android.support.v4.app.FragmentTabHost>

    <FrameLayout
        android:id="@+id/realtabcontent"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

Аудиоконтейнерфрагменткласс

public class AudioContainerFragmentClass extends Fragment implements
        OnClickListener {

    final String TAG = "AudioContainerFragmentClass";
    private Boolean mIsViewInitiated = false;
    private boolean addToBackStack = true;
    private Button bNextFragment;
    private LinearLayout linearLayout;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        try {
            Log.e("AudioContainerFragmentClass", "onCreateView called");
            linearLayout = (LinearLayout) inflater.inflate(
                    R.layout.audio_fragment_container, container, false);
        } catch (Exception e) {
            printException(e.toString());
        }
        return linearLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        try {
            super.onActivityCreated(savedInstanceState);
            Log.e("AudioContainerFragmentClass", "onActivityCreated called");
            if (!mIsViewInitiated) {
                mIsViewInitiated = true;
                initView();
            }
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    private void initView() {
        try {
            Log.e("AudioContainerFragmentClass", "initView called");
            bNextFragment = (Button) linearLayout
                    .findViewById(R.id.bNextFragment);
            bNextFragment.setOnClickListener(this);
            replaceFragment(new AudioFragment(), false);
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    private void replaceFragment(AudioFragment audioFragment, boolean b) {
        try {
            FragmentTransaction ft = getChildFragmentManager()
                    .beginTransaction();
            if (addToBackStack) {
                ft.addToBackStack(null);
            }
            ft.replace(R.id.audio_sub_fragment, audioFragment);
            ft.commit();
            getChildFragmentManager().executePendingTransactions();
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    // Called from FagTabHostMain Activity
    public boolean popFragment() {
        boolean isPop = false;
        try {
            Log.e("AudioContainerFragmentClass", "popFragment called");
            if (getChildFragmentManager().getBackStackEntryCount() > 0) {
                isPop = true;
                getChildFragmentManager().popBackStack();
            }
        } catch (Exception e) {
            printException(e.toString());
        }
        return isPop;
    }

    @Override
    public void onClick(View arg0) {
        TextView tv  = (TextView)getActivity().findViewById(R.id.tvaudioTitle);
        tv.setText("Text changed");
    }

    private void printException(String string) {
        Log.e("__ERRORR__", string);
    }
}

аудиофрагмент

public class AudioFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.audio_sub_fragment, container,
                false);
        return view;
    }
}

person reiley    schedule 12.10.2013    source источник
comment
Какой минимальный SDK вы поддерживаете?   -  person SBerg413    schedule 16.10.2013
comment
Android API версии 10   -  person reiley    schedule 17.10.2013
comment
Пожалуйста, проверьте: stackoverflow.com/questions/26070595/   -  person Thanh Nguyen Van    schedule 05.08.2018


Ответы (4)


Было то же самое в моем приложении. Вам нужно будет скопировать FragmentTabHost в свой проект, укажите в своем коде использование нового пользовательского FragmentTabHost, а затем измените код doTabChanged на следующую реализацию:

    private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
    TabInfo newTab = null;
    for (int i=0; i<mTabs.size(); i++) {
        TabInfo tab = mTabs.get(i);
        if (tab.tag.equals(tabId)) {
            newTab = tab;
        }
    }
    if (newTab == null) {
        throw new IllegalStateException("No tab known for tag " + tabId);
    }
    if (mLastTab != newTab) {
        if (ft == null) {
            ft = mFragmentManager.beginTransaction();
        }
        if (mLastTab != null) {
            if (mLastTab.fragment != null) {
                ft.hide(mLastTab.fragment);
            }
        }
        if (newTab != null) {
            if (newTab.fragment == null) {
                newTab.fragment = Fragment.instantiate(mContext,
                        newTab.clss.getName(), newTab.args);
                ft.add(mContainerId, newTab.fragment, newTab.tag);
                findViewById(mContainerId).setContentDescription("DEBUG. add fragment to this container");
            } else {
                if (newTab.fragment.isHidden()){
                    ft.show(newTab.fragment);
                }
                else{
                    ft.attach(newTab.fragment); 
                }
            }
        }

        mPreviousTab = mLastTab;
        mLastTab = newTab;
    }
    return ft;
}

Изменение, которое было сделано, заключается в том, что вместо deattach/attach фрагмента мы делаем hide/show

person Sean    schedule 19.10.2013
comment
Хорошая логика, попробую. Какое значение mTabs хранится в третьей строке. Кроме того, вы разместили полный фрагмент где-нибудь для справки. Также как позвонить doTabChanged - person reiley; 20.10.2013
comment
@ raul8, полный фрагмент кода практически является оригинальным FragmentTabHost, только с изменениями, которые я опубликовал выше. Чтобы начать использовать этот код, просто 1. скопируйте весь код в новый класс вашего проекта (код можно найти здесь: grepcode.com/file/repository.grepcode.com/java/ext /) 2. Замените код метода на тот, который я разместил. 3. В своем FagTabHostMain классе используйте новый класс для вашего члена `FragmentTabHost mTabHost;` - person Sean; 20.10.2013
comment
Спасибо @Sean за ваш ответ. Но я думаю, вы не разместили здесь весь свой код. Потому что при повороте экрана у меня возникла головная боль, потому что вы скрываете предыдущую вкладку с помощью mLastTab с этим кодом if (mLastTab != null) { if (mLastTab.fragment != null) { ft.hide(mLastTab.fragment); } }. Вы должны показать эту предыдущую скрытую вкладку в onAttachedToWindow с кодом типа if(tab.fragment != null && tab.fragment.isHidden()){ ft.show(tab.fragment); } непосредственно перед тем, как отсоединить фрагмент. - person hermannovich; 12.08.2015
comment
FragmentTabHost.java конфликтует с моей библиотекой android-support-v4-appcompat. Я получаю сообщение об ошибке: - Невозможно выполнить dex: Несколько файлов dex определяют Landroid/support/v4/app/FragmentTabHost$DummyTabFactory. Я использую eclipse для этого проекта. - person Likith Ts; 25.08.2015
comment
я думаю, что ваш код не изменен правильно, счетчик кода четвертой строки с конца является ошибкой. Я думаю, что следует изменить метод прикрепления на метод показа - person perry; 19.08.2016

Я считаю, что ваш фрагмент повторно создается каждый раз, когда вы переключаете вкладку, что означает, что ваши переменные поля сбрасываются.

Вы, вероятно, могли бы использовать пакет saveInstance для управления состоянием вашего фрагмента, но я считаю более полезным и простым использовать SharedPreferences. Это также имеет преимущество сохранения сохраненного состояния, даже если ваше приложение перезапускается.

Для чтения и записи переменных в SharedPreferences я использую этот небольшой вспомогательный класс:

public class PreferencesData {

public static void saveString(Context context, String key, String value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putString(key, value).commit();
}

public static void saveInt(Context context, String key, int value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putInt(key, value).commit();
}


public static void saveBoolean(Context context, String key, boolean value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putBoolean(key, value).commit();
}

public static int getInt(Context context, String key, int defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getInt(key, defaultValue);
}

public static String getString(Context context, String key, String defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getString(key, defaultValue);
}

public static boolean getBoolean(Context context, String key, boolean defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getBoolean(key, defaultValue);
}
}

Теперь, как пример, чтобы сохранить вашу переменную mIsViewInitiated, затем в onPause:

@Override
protected void onPause() {
    PreferencesData.saveBoolean(this, "isViewInitiated", mIsViewInitiated);
    super.onPause();
}

И чтобы получить его снова:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    try {
        super.onActivityCreated(savedInstanceState);
        Log.e("AudioContainerFragmentClass", "onActivityCreated called");
        // will now be true if onPause have been called
        mIsViewInitiated = PreferencesData.getBoolean(this, "isViewInitiated", false);
        if (!mIsViewInitiated) {
            mIsViewInitiated = true;
            initView();
        }
    } catch (Exception e) {
        printException(e.toString());
    }
}

Поскольку в этом примере переменная сообщает, был ли загружен какой-либо пользовательский интерфейс, вы можете установить для нее значение false при уничтожении активности.

@Override
protected void onDestroy() {
    PreferencesData.saveBoolean(this, "isViewInitiated", false);
    super.onDestroy();
}

Этот ответ является всего лишь одним вариантом и показывает мои личные предпочтения, тогда как другие варианты могут лучше подойти для вашей ситуации. Я бы посоветовал взглянуть на http://developer.android.com/guide/topics/data/data-storage.html

person cYrixmorten    schedule 14.10.2013
comment
Ваш ответ был очень хорошим, но я пытался избежать общих настроек, поскольку данные, которые нужно сохранить, были в большом количестве. Даже ответ, предоставленный Шоном, требует дальнейшей обработки, но он дает мне начало. Спасибо - person reiley; 21.10.2013
comment
Без проблем. Поскольку я не вижу ответов в этом направлении, я бы предложил, возможно, посмотреть демонстрационные примеры API сохранения фрагмента GoogleMap. Я думаю, что вы должны иметь возможность комбинировать setRetainInstance(true) и сохранять ссылку на фрагменты, чтобы восстановить правильный (сохраненный) фрагмент при изменении вкладок. Просто дайте вам еще один вариант для просмотра. - person cYrixmorten; 21.10.2013

Измените свою активность, чтобы переопределить onSaveInstanceState и ваш метод onCreate для восстановления из «сохраненногоInstanceState».

public static final String TAB_STATE = "TAB_STATE";

@Override
protected void onSaveInstanceState(Bundle outState) {
    outstate.putParcelable(TAB_STATE, mTabHost.onSaveInstanceState());
    super.onSaveInstanceState(outState);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_fag_tab_host_main);

    mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
    mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
    if(savedInstanceState==null || savedInstanceState.getParcelable(TAB_STATE)==null){
    mTabHost.addTab(mTabHost.newTabSpec("audio").setIndicator("Audio"),
            AudioContainerFragmentClass.class, null);
    mTabHost.addTab(mTabHost.newTabSpec("video").setIndicator("Video"),
            VideoContainerFragmentClass.class, null);
    } else{
        mTabHost.onRestoreInstanceState(savedInstanceState.getParcelable(TAB_STATE));
    }

}
person ejohansson    schedule 16.10.2013
comment
Спасибо за ответ, но не удалось вызвать mTabHost.onSaveInstanceState() внутри метода onSaveInstanceState(). Это работает для вас? - person reiley; 18.10.2013

Как указано выше, вы можете сохранить, а затем восстановить данные с помощью Bundle, Shared Preferences или SQLite db. Вы также можете вызвать setRetainInstance(true) в своем фрагменте. Это предотвратит повторное создание ваших фрагментов.

person SoundsDangerous    schedule 17.10.2013
comment
Я попробовал этот метод, но не смог сохранить состояние. Может быть, я делал это неправильно. Не могли бы вы объяснить на примере? - person reiley; 18.10.2013