Como retomar o Fragment do BackStack, se existir

151

Estou aprendendo a usar fragmentos. Eu tenho três instâncias Fragmentque são inicializadas na parte superior da classe. Estou adicionando o fragmento a uma atividade como esta:

Declarando e inicializando:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Substituindo / Adicionando:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Esses trechos estão funcionando corretamente. Cada fragmento é anexado à atividade e é salvo na pilha traseira sem nenhum problema.

Então, quando eu lançar A, Ce em seguida B, os olhares pilha como este:

| |
|B|
|C|
|A|
___

E quando pressiono o botão 'voltar', Bé destruído e Cretomado.

Mas, quando inicio o fragmento Apela segunda vez, em vez de retomar da pilha traseira, ele é adicionado na parte superior da pilha traseira

| |
|A|
|C|
|A|
___

Mas quero retomar Ae destruir todos os fragmentos em cima (se houver). Na verdade, eu apenas gosto do comportamento padrão da pilha traseira.

Como eu faço isso?

Esperado: ( Adeve ser retomado e os principais fragmentos devem ser destruídos)

| |
| |
| |
|A|
___

Editar: (sugerido por A - C)

Este é o meu código de tentativa:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

O problema é que, quando inicio Ae B, em seguida, pressiono 'voltar', Bé removido e Aretomado. e pressionar 'voltar' uma segunda vez deve sair do aplicativo. Mas está mostrando uma janela em branco e eu tenho que pressionar uma terceira vez para fechá-la.

Além disso, quando inicio A, então B, então C, Bnovamente ...

Esperado:

| |
| |
|B|
|A|
___

Real:

| |
|B|
|B|
|A|
___

Devo substituir onBackPressed()qualquer personalização ou estou perdendo alguma coisa?

Kaidul
fonte

Respostas:

279

Lendo a documentação , há uma maneira de exibir a pilha de trás com base no nome da transação ou no ID fornecido pelo commit. Usar o nome pode ser mais fácil, pois não deve exigir o controle de um número que pode mudar e reforça a lógica da "entrada de pilha traseira única".

Como você deseja apenas uma entrada da pilha traseira por Fragment, faça com que o nome do estado de retorno seja o nome da classe do Fragmento (via getClass().getName()). Em seguida, ao substituir a Fragment, use o popBackStackImmediate()método Se retornar true, significa que há uma instância do fragmento na pilha de trás. Caso contrário, execute a lógica de substituição do fragmento.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

EDITAR

O problema é - quando inicio A e B, pressione o botão Voltar, B é removido e A é retomado. e pressionar novamente o botão Voltar deve sair do aplicativo. Mas está mostrando uma janela em branco e precisa ser pressionada novamente para fechá-la.

Isso ocorre porque ele FragmentTransactionestá sendo adicionado à pilha traseira para garantir que possamos colocar os fragmentos no topo mais tarde. Uma solução rápida para isso é substituir onBackPressed()e finalizar a Atividade se a pilha traseira contiver apenas 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Com relação às entradas duplicadas da pilha traseira, sua declaração condicional que substitui o fragmento se ele não foi populado é claramente diferente do que meu snippet de código original. O que você está fazendo é adicionar à pilha traseira, independentemente de a pilha traseira ter ou não sido exibida.

Algo assim deve estar mais próximo do que você deseja:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

O condicional foi alterado um pouco, pois a seleção do mesmo fragmento enquanto estava visível também causou entradas duplicadas.

Implementação:

Eu sugiro não replaceFragment()desmontar o método atualizado como você fez no seu código. Toda a lógica está contida neste método e a movimentação de peças pode causar problemas.

Isso significa que você deve copiar o replaceFragment()método atualizado em sua classe e depois alterar

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

então é simplesmente

replaceFragment (fragmentName);

EDIT # 2

Para atualizar a gaveta quando a pilha de trás for alterada, crie um método que aceite em um fragmento e compare os nomes de classe. Se algo corresponder, altere o título e a seleção. Adicione também OnBackStackChangedListenere chame seu método de atualização se houver um fragmento válido.

Por exemplo, nas atividades onCreate(), adicione

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

E o outro método:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Agora, sempre que a pilha de trás for alterada, o título e a posição verificada refletirão o visível Fragment.

A - C
fonte
Obrigado pela sua resposta. :) Está funcionando parcialmente. Eu editei minha pergunta com progresso. Por favor, dê uma olhada)
Kaidul
2
@RishabhSrivastava lembre-se de que um "pop" geralmente destrói o fragmento mais alto. Quanto ao método chamado primeiro, depende - veja o Ciclo de vida do fragmento. No seu caso, eu usaria um OnBackstackChangedListenerpara que você possa garantir que está lidando com o fragmento correto.
A - C
2
@RishabhSrivastava Foi por isso que sugeri o OnBackstackChangedListenermesmo.
A - C
1
@AlexanderFarber Você está correto. Quando eu fiz essa resposta, eu não estava pensando em instanceof:)
A - C
2
Sua solução parece boa, mas se você tiver três fragmentos, clique neles A-B-C-Be pressione uma vez, você voltará Ae não voltará C. Isso ocorre porque popBackStackImmediatenão apenas aparece a tag fornecida, mas também tudo acima. Existe algum trabalho por aí?
Raeglan
10

Penso que este método pode resolver o seu problema:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

publicado originalmente nesta pergunta

erluxman
fonte
Informe-me se responder dessa forma é contra as regras que eu queria facilitar para as pessoas verem neste post. Obrigado ..
erluxman
Não tenho a certeza se é contra as regras (provavelmente não), mas este é definitivamente útil
Kathir
1
Qual é o uso da terceira linha? -> manager.findFragmentByTag (tag); Parece que ele não está fazendo nada ..
Rik van Velzen
3

Etapa 1: implemente uma interface com sua classe de atividade

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Etapa 2: quando você chamar o outro fragmento, adicione este método:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
Vinod Joshi
fonte
2

Sei que é tarde demais para responder a essa pergunta, mas resolvi esse problema sozinho e achei que valeria a pena compartilhá-lo com todos.`

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

E no onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Vivek Pratap Singh
fonte
1
@ X-Black ... BaseFragment é uma classe base para cada fragmento usado no aplicativo.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});
gushedaoren
fonte
0

Solução mais fácil mudará essa linha

ft.replace(R.id.content_frame, A); para ft.add(R.id.content_frame, A);

E dentro do seu layout XML, use

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable significa que ele pode ser clicado por um dispositivo apontador ou toque em um dispositivo de toque.

Focusablesignifica que ele pode obter o foco de um dispositivo de entrada como um teclado. Dispositivos de entrada, como teclados, não podem decidir para qual exibição enviar seus eventos de entrada com base nas próprias entradas; portanto, eles os enviam para a exibição em foco.

Hossam Hassan
fonte