android - fragments - Cómo reanudar Fragment from BackStack si existe
get fragments from backstack (4)
Estoy aprendiendo a usar fragmentos. Tengo tres instancias de Fragment
que se inicializan en la parte superior de la clase. Estoy agregando el fragmento a una actividad como esta:
Declarando e inicializando:
Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();
Reemplazar / Agregar:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();
Estos fragmentos están funcionando correctamente. Cada fragmento se adjunta a la actividad y se guarda en la pila de respaldo sin ningún problema.
Entonces, cuando ejecuto A
, C
y luego B
, la pila se ve así:
| |
|B|
|C|
|A|
___
Y cuando presiono el botón ''atrás'', B
se destruye y C
se reanuda.
Pero, cuando lanzo el fragmento A
por segunda vez, en lugar de reanudar desde la parte posterior de la pila, se agrega en la parte superior de la pila posterior
| |
|A|
|C|
|A|
___
Pero quiero reanudar A
y destruir todos los fragmentos en la parte superior (si corresponde). En realidad, me gusta el comportamiento predeterminado de la pila trasera.
¿Cómo logro esto?
Esperado: ( A
debe reanudarse y los fragmentos superiores deben ser destruidos)
| |
| |
| |
|A|
___
Editar: (sugerido por A - C)
Este es mi código de prueba:
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);
}
El problema es que cuando abro A
y luego B
, presiono ''Atrás'', B
se elimina y A
se reanuda. y presionar ''volver'' por segunda vez debería salir de la aplicación. Pero muestra una ventana en blanco y tengo que presionar una tercera vez para cerrarla.
Además, cuando abro A
, luego B
, luego C
, luego B
nuevo ...
Esperado:
| |
| |
|B|
|A|
___
Real:
| |
|B|
|B|
|A|
___
¿Debo anular onBackPressed()
con cualquier personalización o me falta algo?
Al leer la documentation , hay una forma de abrir la pila posterior en función del nombre de la transacción o la identificación provista por commit. Usar el nombre puede ser más fácil ya que no debería requerir el seguimiento de un número que puede cambiar y refuerza la lógica de "entrada única de la pila posterior".
Dado que solo desea una entrada de la pila de respaldo por Fragment
, establezca el nombre del estado reverso como el nombre de clase del Fragmento (a través de getClass().getName()
). Luego, cuando reemplaces un Fragment
, usa el método popBackStackImmediate()
. Si devuelve verdadero, significa que hay una instancia del Fragmento en la pila posterior. De lo contrario, ejecute la lógica de reemplazo del 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
El problema es que cuando abro A y luego B, presiona el botón Atrás, B se elimina y A se reanuda. y presionar nuevamente el botón Atrás debe salir de la aplicación. Pero muestra una ventana en blanco y necesita otra prensa para cerrarla.
Esto se debe a que FragmentTransaction
se está agregando a la pila posterior para garantizar que podamos mostrar los fragmentos en la parte superior más adelante. Una solución rápida para esto es anular onBackPressed()
y finalizar la Actividad si la pila posterior contiene solo 1 Fragment
@Override
public void onBackPressed(){
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
finish();
}
else {
super.onBackPressed();
}
}
En cuanto a las entradas duplicadas de la pila posterior, su declaración condicional que reemplaza el fragmento si no se ha reventado es claramente diferente de lo que es mi fragmento de código original. Lo que estás haciendo es agregar a la pila de respaldo sin importar si la pila de respaldo se rompió o no.
Algo así debería estar más cerca de lo que quieres:
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();
}
}
El condicional se modificó un poco, ya que seleccionar el mismo fragmento mientras estaba visible también causaba entradas duplicadas.
Implementación:
Recomiendo encarecidamente no tomar el método actualizado replaceFragment()
como lo hizo en su código. Toda la lógica está contenida en este método y las partes móviles pueden causar problemas.
Esto significa que debe copiar el replaceFragment()
actualizado replaceFragment()
en su clase y luego cambiar
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();
entonces es simplemente
replaceFragment (fragmentName);
EDIT # 2
Para actualizar el cajón cuando cambia la pila de respaldo, cree un método que acepte en un Fragmento y compare los nombres de clase. Si algo coincide, cambie el título y la selección. También agregue un OnBackStackChangedListener
y OnBackStackChangedListener
que llame a su método de actualización si hay un Fragmento válido.
Por ejemplo, en Activity onCreate()
, agregue
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
if (f != null){
updateTitleAndDrawer (f);
}
}
});
Y el otro 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
}
}
Ahora, cada vez que la pila trasera cambie, el título y la posición marcada reflejarán el Fragment
visible.
Creo que este método puede resolver tu problema:
public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {
FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
manager.findFragmentByTag ( tag );
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 ();
}
}
que fue publicado originalmente en esta pregunta
Paso 1: Implementa una interfaz con tu clase de actividad
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
}
}
Paso 2: cuando llame al otro fragmento, agregue 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();
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if(getFragmentManager().getBackStackEntryCount()==0) {
onResume();
}
}
});