android fragment- Como salvar estados de visualizações em um fragmento quando outro fragmento é empurrado em cima dele

137

No android, um fragmento (digamos FragA) é adicionado ao backstack e outro fragmento (digamos FragB) chega ao topo. Agora, ao voltar, FragAchega ao topo e onCreateView()é chamado. Agora eu estava FragAem um estado específico antes de FragBser empurrado para cima.

Minha pergunta é como posso restaurar FragApara o estado anterior? Existe uma maneira de salvar o estado (como, por exemplo, em um pacote) e, em caso afirmativo, qual método devo substituir?

pankajagarwal
fonte

Respostas:

98

No exemplo de guia FragmentList do fragmento, você pode encontrar:

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

Que você pode usar mais tarde, assim:

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

Eu sou iniciante em Fragmentos, mas parece que a solução do seu problema;) OnActivityCreated é invocada após o retorno do fragmento da pilha traseira.

ania
fonte
18
Não era possível fazer com que isso funcionasse. O SaveInstanceState sempre foi nulo. Estou adicionando fragmento via layout xml. Teve que mudar o mCurCheckPosition para estático, então ele funciona, mas parece hacky.
22412 scottyab
57
não chama onSaveInstanceState - por que faria? Portanto, essa abordagem não funciona.
meia
10
Essa abordagem realmente funcionará caso desejemos manter o estado do fragmento ao retornar de outro fragmento na mesma atividade? onSaveInstanceState () é chamado apenas nos eventos Activity onPause / onStop. De acordo com a documentação: "Também como uma atividade, você pode reter o estado de um fragmento usando um Bundle, caso o processo da atividade seja interrompido e você precise restaurar o estado do fragmento quando a atividade for recriada. Você pode salvar o estado durante o retorno de chamada onSaveInstanceState () do fragmento e restaurá-lo durante onCreate (), onCreateView () ou onActivityCreated (). "
Paramvir Singh
26
Para que conste, essa abordagem está errada e não deve estar perto dos votos positivos que possui. onSaveInstanceStatesó é chamado quando a atividade correspondente também está sendo desligada.
Martin Konecny
16
onSaveInstanceState () é chamado onle quando as mudanças de configuração ocorreu e a atividade é destruído, essa resposta está errada
Tadas Valaitis
83

Os fragmentos onSaveInstanceState(Bundle outState)nunca serão chamados, a menos que a atividade do fragmento invoque a si mesma e os fragmentos anexados. Portanto, esse método não será chamado até que algo (normalmente rotação) force a atividade SaveInstanceStatee a restaure posteriormente. Porém, se você tiver apenas uma atividade e um grande conjunto de fragmentos (com uso intensivo de replace) e o aplicativo for executado apenas em uma atividade de orientação, onSaveInstanceState(Bundle outState)poderá não ser chamada por muito tempo.

Conheço três soluções possíveis.

O primeiro:

use os argumentos do fragmento para armazenar dados importantes:

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

A segunda maneira, mas menos pedante - mantenha variáveis ​​em singletons

O terceiro - não replace()fragmentos, mas add()/ show()/ hide()eles.

Fyodor Volchyok
fonte
3
A melhor solução quando Fragment.onSaveInstanceState()nunca foi chamado. Apenas salve seus próprios dados no argumento, incluindo os itens na exibição em lista ou apenas seus IDs (se você tiver outro gerenciador de dados centralizado). Não há necessidade de salvar a posição da exibição de lista - que foi salva e restaurada automaticamente.
John Pang
Eu tentei usar o seu exemplo no meu aplicativo, mas isto: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);é sempre null. Qual é o problema?
Fron 7/03/15
Usar o fragmento getArguments()é DEFINITIVAMENTE o caminho a seguir, incluindo fragmentos aninhados em um ViewPager. Uso 1 atividade e troco muitos fragmentos de entrada / saída, e isso funciona perfeitamente. Aqui está um teste simples para você examinar qualquer solução proposta: 1) vá do fragmento A para o fragmento B; 2) mude a orientação do dispositivo duas vezes; 3) pressione o botão voltar no dispositivo.
Andy H.
Eu tentei a primeira abordagem, mas não funcionou para mim. getArguments () sempre retorna nulo. Também faz sentido porque o fragmento é substituído e em onCreate () você define um novo Bundle para que o antigo Bundle seja perdido. O que estou perdendo e estou errado?
Zvi 31/03
@ Zvi, eu escrevi esse código há 1,5 anos e não lembro de todos os detalhes, mas como me lembro, o substituir não recriar fragmentos, fragmento recriado apenas se você tiver criado uma nova instância do seu código. Nesse caso, obviamente, o construtor chamou e setArguments(new Bundle());substituiu o antigo pacote. Portanto, certifique-se de criar fragmento apenas uma vez e, em seguida, use esta instância em vez de criar uma nova sempre.
Fyodor Volchyok 31/03
20

Observe que, se você trabalha com fragmentos usando o ViewPager, é muito fácil. Você só precisa chamar esse método: setOffscreenPageLimit().

De acordo com os documentos:

Defina o número de páginas que devem ser mantidas nos dois lados da página atual na hierarquia de exibição em um estado ocioso. As páginas além desse limite serão recriadas do adaptador quando necessário.

Problema semelhante aqui

véspera
fonte
5
Isso é diferente. setOffScreenPageLimit age como um cache (ou seja, quantas páginas o ViewPager precisa manipular em um determinado momento), mas não é usado para salvar o estado de um fragmento.
Renaud Mathieu
No meu caso, funcionou com setOffscreenPageLimit () - embora os fragmentos tenham sido destruídos, o estado da exibição foi salvo e restaurado.
Davincho
Obrigado, me ajudou também.
Elias
Anos depois e isso ainda é tão relevante. Embora ele realmente não responder à pergunta, ele resolve um problema
Supremo Dolphin
19

Basta inflar sua visualização pela primeira vez.

Exemplo a seguir:

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
fonte
também deve manter fragmentos existentes na matriz ou algo
Amir
Ouvi dizer que manter uma referência ao rootView de um fragmento é uma prática ruim - poderia resultar em vazamentos [citação necessário]?
giraffe.guru
1
@ giraffe.guru Fragmentrefere-se à sua visualização raiz não fará isso. Embora a referência por alguns elementos da raiz do GC , como propriedade estática global, variável de thread não-ui. Uma Fragmentinstância não é raiz da GC, portanto pode ser coletada como lixo. O mesmo acontecerá com sua visão raiz.
LYM Zoy
Você é o mesmo dia.
Vijendra patidar 14/02
Doce e simples.
Rupam Das
8

Eu trabalhei com um problema muito parecido com isso. Como eu sabia que voltaria frequentemente a um fragmento anterior, verifiquei se o fragmento .isAdded()era verdadeiro e, se sim, em vez de fazer um transaction.replace(), apenas faço a transaction.show(). Isso evita que o fragmento seja recriado se já estiver na pilha - não é necessário salvar o estado.

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

Outra coisa a ter em mente é que, embora isso preserve a ordem natural dos fragmentos, você ainda precisará lidar com a atividade que está sendo destruída e recriada com a mudança de orientação (config). Para contornar isso no AndroidManifest.xml para o seu nó:

android:configChanges="orientation|screenSize"

No Android 3.0 e superior, o screenSizeé aparentemente necessário.

Boa sorte

rmirabelle
fonte
transaction.addToBackStack (button_id + "stack_item"); // o que essa linha faz.O que é button_id aqui?
Raghu_3 17/03
button_id é apenas uma variável composta. O argumento de cadeia passado para addToBackStack é apenas um nome opcional para o estado de backstack - você pode configurá-lo como nulo se estiver gerenciando apenas um único backstack.
precisa
, eu estou tendo um problema semelhante a este com fragmentos, você pode por favor olhar para ele- stackoverflow.com/questions/22468977/...
raghu_3
1
Nunca adicione android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsseu manifesto. Em vez disso, aprenda como salvar e restaurar o estado, por exemplo, aqui: speakerdeck.com/cyrilmottier/…
Marcin Koziński
E depois que você aprender o quão insanamente difícil é o estado de economia e que a solução apresentada resolve o problema de maneira mais limpa em sua situação específica, vá em frente e use-o, sem se preocupar com votos negativos daqueles que discordam ;-)
rmirabelle
5

A melhor solução que encontrei está abaixo:

onSavedInstanceState (): sempre chamado dentro do fragmento quando a atividade é encerrada (mova a atividade de um para outro ou as alterações de configuração). Portanto, se estivermos chamando vários fragmentos na mesma atividade, precisamos usar a seguinte abordagem:

Use OnDestroyView () do fragmento e salve o objeto inteiro dentro desse método. Em seguida, OnActivityCreated (): verifique se o objeto é nulo ou não (porque esse método chama sempre). Agora restaure o estado de um objeto aqui.

Funciona sempre!

Aman Goel
fonte
4

se você estiver lidando com as alterações de configuração em sua atividade de fragmento especificada no manifesto do Android como esta

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

então o onSaveInstanceStatefragmento não será chamado e o savedInstanceStateobjeto sempre será nulo.

Brook Oldre
fonte
1

Não acho que onSaveInstanceStateseja uma boa solução. apenas use para atividades que foram destruídas.

No Android 3.0, o Fragmen foi gerenciado pelo FragmentManager, a condição é: um mapeamento de atividade de manny fragmentos, quando o fragmento é adicionado (não substitua: será recriado) no backStack, a visualização será destruída. quando voltar ao último, ele será exibido como antes.

Então eu acho que o fragmentManger e a transação são bons o suficiente para lidar com isso.

smallfatter
fonte
0

Eu usei uma abordagem híbrida para fragmentos que contêm uma exibição de lista. Parece ter bom desempenho, já que não substituo o fragmento atual, mas adiciono o novo fragmento e oculto o atual. Eu tenho o seguinte método na atividade que hospeda meus fragmentos:

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

Eu uso esse método no meu fragmento (contendo a exibição de lista) sempre que um item da lista é clicado / tocado (e, portanto, preciso iniciar / exibir o fragmento de detalhes):

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()retorna uma matriz de seqüências de caracteres que eu uso como tags para diferentes fragmentos quando adiciono um novo fragmento (consulte o transaction.addmétodo no addFragmentmétodo acima).

No fragmento que contém a exibição de lista, faço isso no método 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();
}

Em seguida, no onCreateView do fragmento (na verdade, em um método que é chamado no onCreateView), restauro o estado:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
fonte
0

No final, depois de tentar muitas dessas soluções complicadas, eu só precisava salvar / restaurar um único valor no meu Fragmento (o conteúdo de um EditText) e, apesar de não ser a solução mais elegante, criar uma SharedPreference e armazenar meu estado trabalhou para mim

VMMF
fonte
0

Uma maneira simples de manter os valores dos campos em diferentes fragmentos em uma atividade

Crie as instâncias de fragmentos e adicione em vez de substituir e remover

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

Em seguida, apenas mostre e oculte os fragmentos em vez de adicioná-los e removê-los novamente

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

;

Sreejesh K Nair
fonte
-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) {
}
Sathish Kumar
fonte
5
Considere incluir algumas informações sobre sua resposta, em vez de simplesmente postar o código. Tentamos fornecer não apenas 'correções', mas ajudar as pessoas a aprender. Você deve explicar o que estava errado no código original, o que você fez de maneira diferente e por que suas alterações funcionaram.
Andrew Barber