para - manual de programacion android pdf
Los fragmentos anidados desaparecen durante la animación de transición (13)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@
EDITAR: terminé implementando esta solución ya que había otros problemas que esto tiene. Square recientemente salió con 2 bibliotecas que reemplazan fragmentos. Yo diría que esto podría ser una mejor alternativa que tratar de hackear fragmentos para hacer algo que Google no quiere que hagan.
http://corner.squareup.com/2014/01/mortar-and-flow.html
@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Pensé que pondría esta solución para ayudar a las personas que tienen este problema en el futuro. Si rastrea la conversación de los carteles originales con otras personas y mira el código que publicó, verá que el póster original finalmente llega a la conclusión de utilizar una animación no operativa en los fragmentos secundarios mientras anima el fragmento principal. Esta solución no es ideal, ya que te obliga a realizar un seguimiento de todos los fragmentos secundarios, lo que puede ser engorroso al usar un ViewPager con FragmentPagerAdapter.
Como utilizo fragmentos para niños en todo el lugar, se me ocurrió esta solución que es eficiente y modular (por lo que se puede eliminar fácilmente) en caso de que alguna vez lo arreglen y esta animación no operativa ya no se necesita.
Hay muchas formas de implementar esto. Elegí usar un singleton, y lo llamo ChildFragmentAnimationManager. Básicamente, hará un seguimiento de un fragmento de niño en función de su padre y aplicará una animación no operativa a los niños cuando se lo pidan.
public class ChildFragmentAnimationManager {
private static ChildFragmentAnimationManager instance = null;
private Map<Fragment, List<Fragment>> fragmentMap;
private ChildFragmentAnimationManager() {
fragmentMap = new HashMap<Fragment, List<Fragment>>();
}
public static ChildFragmentAnimationManager instance() {
if (instance == null) {
instance = new ChildFragmentAnimationManager();
}
return instance;
}
public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) {
List<Fragment> children = getChildren(parent);
ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim);
for (Fragment child : children) {
ft.remove(child);
}
return ft;
}
public void putChild(Fragment parent, Fragment child) {
List<Fragment> children = getChildren(parent);
children.add(child);
}
public void removeChild(Fragment parent, Fragment child) {
List<Fragment> children = getChildren(parent);
children.remove(child);
}
private List<Fragment> getChildren(Fragment parent) {
List<Fragment> children;
if ( fragmentMap.containsKey(parent) ) {
children = fragmentMap.get(parent);
} else {
children = new ArrayList<Fragment>(3);
fragmentMap.put(parent, children);
}
return children;
}
}
Luego necesitas tener una clase que amplíe Fragmento para que todos tus Fragmentos se extiendan (al menos tus Fragmentos secundarios). Ya tenía esta clase, y la llamo BaseFragment. Cuando se crea una vista de fragmentos, la agregamos al ChildFragmentAnimationManager y la eliminamos cuando se destruye. Puede hacer esto en Conectar / Desconectar, u otros métodos coincidentes en la secuencia. Mi lógica para elegir Crear / Destruir vista era porque si un Fragmento no tiene una Vista, no me importa animarlo para que se siga viendo. Este enfoque también debería funcionar mejor con ViewPagers que usan Fragmentos ya que no mantendrá un registro de cada Fragmento que contiene un FragmentPagerAdapter, sino solo 3.
public abstract class BaseFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Fragment parent = getParentFragment();
if (parent != null) {
ChildFragmentAnimationManager.instance().putChild(parent, this);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onDestroyView() {
Fragment parent = getParentFragment();
if (parent != null) {
ChildFragmentAnimationManager.instance().removeChild(parent, this);
}
super.onDestroyView();
}
}
Ahora que todos sus Fragmentos están almacenados en la memoria por el fragmento principal, puede invocar animaciones sobre ellos de esta manera, y los fragmentos secundarios no desaparecerán.
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this)
.setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
.replace(R.id.container, f)
.addToBackStack(null)
.commit();
Además, para que lo tengas, aquí está el archivo no_anim.xml que va en tu carpeta res / anim:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator">
<translate android:fromXDelta="0" android:toXDelta="0"
android:duration="1000" />
</set>
De nuevo, no creo que esta solución sea perfecta, pero es mucho mejor que para cada instancia tener un Fragmento hijo, implementando un código personalizado en el fragmento principal para hacer un seguimiento de cada niño. He estado allí, y no es divertido.
Aquí está el escenario: la actividad contiene el fragmento A
, que a su vez usa getChildFragmentManager()
para agregar los fragmentos A1
y A2
en su onCreate
así:
getChildFragmentManager()
.beginTransaction()
.replace(R.id.fragmentOneHolder, new FragmentA1())
.replace(R.id.fragmentTwoHolder, new FragmentA2())
.commit()
Hasta ahora, todo bien, todo está funcionando como se esperaba.
Luego ejecutamos la siguiente transacción en la Actividad:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.replace(R.id.fragmentHolder, new FragmentB())
.addToBackStack(null)
.commit()
Durante la transición, las animaciones de enter
para el fragmento B
ejecutan correctamente, pero los fragmentos A1 y A2 desaparecen por completo . Cuando revertimos la transacción con el botón Atrás, se inicializan correctamente y se muestran normalmente durante la animación popEnter
.
En mi breve prueba, se volvió más extraño: si configuro las animaciones para los fragmentos secundarios (ver a continuación), la animación de exit
ejecuta intermitentemente cuando agregamos el fragmento B
getChildFragmentManager()
.beginTransaction()
.setCustomAnimations(enter, exit)
.replace(R.id.fragmentOneHolder, new FragmentA1())
.replace(R.id.fragmentTwoHolder, new FragmentA2())
.commit()
El efecto que quiero lograr es simple: quiero que se popExit
animación de exit
(¿o debería ser popExit
?) En el fragmento A
(anim2), animando todo el contenedor, incluidos sus elementos secundarios anidados.
¿Hay alguna manera de lograr eso?
Editar : encuentra here un caso de prueba
Edit2 : Gracias a @StevenByle por empujarme a seguir intentándolo con las animaciones estáticas. Aparentemente puede establecer animaciones por operación (no global para toda la transacción), lo que significa que los niños pueden tener un conjunto de animación estático indefinido, mientras que sus padres pueden tener una animación diferente y todo se puede comprometer en una transacción . Consulte la discusión a continuación y el here .
Creo que encontré una mejor solución a este problema que hacer una instantánea del fragmento actual con un mapa de bits, como sugirió Luksprog.
El truco consiste en ocultar el fragmento que se está eliminando o extrayendo y solo después de que se hayan completado las animaciones, el fragmento se elimina o se separa en su propia transacción de fragmentos.
Imagina que tenemos FragmentA
y FragmentB
, ambos con subfragmentos. Ahora cuando normalmente harías:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.add(R.id.fragmentHolder, new FragmentB())
.remove(fragmentA) <-------------------------------------------
.addToBackStack(null)
.commit()
En cambio lo haces
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.add(R.id.fragmentHolder, new FragmentB())
.hide(fragmentA) <---------------------------------------------
.addToBackStack(null)
.commit()
fragmentA.removeMe = true;
Ahora para la implementación del Fragmento:
public class BaseFragment extends Fragment {
protected Boolean detachMe = false;
protected Boolean removeMe = false;
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if (nextAnim == 0) {
if (!enter) {
onExit();
}
return null;
}
Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
assert animation != null;
if (!enter) {
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
onExit();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return animation;
}
private void onExit() {
if (!detachMe && !removeMe) {
return;
}
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
if (detachMe) {
fragmentTransaction.detach(this);
detachMe = false;
} else if (removeMe) {
fragmentTransaction.remove(this);
removeMe = false;
}
fragmentTransaction.commit();
}
}
De la respuesta anterior de @kcoppock,
si tiene Actividad-> Fragmento-> Fragmentos (apilamiento múltiple, lo siguiente ayuda), una edición menor a la mejor respuesta en mi humilde opinión.
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
final Fragment parent = getParentFragment();
Fragment parentOfParent = null;
if( parent!=null ) {
parentOfParent = parent.getParentFragment();
}
if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else
if (!enter && parent != null && parent.isRemoving()) {
// This is a workaround for the bug where child fragments disappear when
// the parent is removed (as all children are first removed from the parent)
// See https://code.google.com/p/android/issues/detail?id=55228
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else {
return super.onCreateAnimation(transit, enter, nextAnim);
}
}
Entiendo que esto puede no ser capaz de resolver completamente su problema, pero tal vez se adapte a las necesidades de otra persona, puede agregar las animaciones enter
/ exit
y popEnter
/ popExit
a sus hijos. Fragment
que en realidad no mueven / animan los Fragment
. Siempre que las animaciones tengan la misma duración / desplazamiento que las animaciones de Fragment
originales, parecerán moverse / animarse con la animación de los padres.
Estaba teniendo el mismo problema con el fragmento de mapa. Se mantuvo desapareciendo durante la animación de salida de su fragmento contenedor. La solución consiste en agregar una animación para el fragmento de mapa secundario que lo mantendrá visible durante la animación de salida del fragmento principal. La animación del fragmento hijo mantiene su alfa al 100% durante su período de duración.
Animación: res / animator / keep_child_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="1.0"
android:duration="@integer/keep_child_fragment_animation_duration" />
</set>
La animación se aplica cuando el fragmento de mapa se agrega al fragmento principal.
Fragmento principal
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.map_parent_fragment, container, false);
MapFragment mapFragment = MapFragment.newInstance();
getChildFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0)
.add(R.id.map, mapFragment)
.commit();
return view;
}
Finalmente, la duración de la animación del fragmento hijo se establece en un archivo de recursos.
values / integers.xml
<resources>
<integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
Estoy publicando mi solución para mayor claridad. La solución es bastante simple. Si está tratando de imitar la animación de transacción de fragmentos del padre simplemente agregue una animación personalizada a la transacción del fragmento hijo con la misma duración. Ah, y asegúrese de configurar la animación personalizada antes de agregar ().
getChildFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
.add(R.id.container, nestedFragment)
.commit();
El xml para R.anim.none (el tiempo de entrada / salida de mis padres es de 250ms)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>
Hace poco me encontré con este problema en mi pregunta: fragmentos anidados que transicionan incorrectamente
Tengo una solución que resuelve esto sin guardar un mapa de bits, ni usar el reflejo ni ningún otro método insatisfactorio.
Se puede ver un proyecto de ejemplo aquí: https://github.com/zafrani/NestedFragmentTransitions
Un GIF del efecto se puede ver aquí: https://imgur.com/94AvrW4
En mi ejemplo, hay 6 fragmentos de niños, divididos entre dos fragmentos principales. Puedo lograr las transiciones para entrar, salir, abrir y cerrar sin ningún problema. Los cambios de configuración y retrocesos también se manejan con éxito.
La mayor parte de la solución está en la función CreadaAnimador de BaseFragment (el fragmento extendido por mis hijos y mis fragmentos principales) que se ve así:
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
if (isConfigChange) {
resetStates()
return nothingAnim()
}
if (parentFragment is ParentFragment) {
if ((parentFragment as BaseFragment).isPopping) {
return nothingAnim()
}
}
if (parentFragment != null && parentFragment.isRemoving) {
return nothingAnim()
}
if (enter) {
if (isPopping) {
resetStates()
return pushAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return enterAnim()
}
if (isPopping) {
resetStates()
return popAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return exitAnim()
}
La actividad y el fragmento principal son responsables de establecer los estados de estos booleanos. Es más fácil ver cómo y dónde desde mi proyecto de ejemplo.
No estoy usando fragmentos de soporte en mi ejemplo, pero la misma lógica se puede usar con ellos y su función onCreateAnimation
Para animar la desaparición de los fragmentos combinados, podemos forzar el pop back stack en ChildFragmentManager. Esto disparará la animación de transición. Para hacer esto, tenemos que recuperar el evento OnBackButtonPressed o escuchar los cambios de backstack.
Aquí hay un ejemplo con código.
View.OnClickListener() {//this is from custom button but you can listen for back button pressed
@Override
public void onClick(View v) {
getChildFragmentManager().popBackStack();
//and here we can manage other fragment operations
}
});
Fragment fr = MyNeastedFragment.newInstance(product);
getChildFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.replace(R.neasted_fragment_container, fr)
.addToBackStack("Neasted Fragment")
.commit();
Para evitar que el usuario vea desaparecer los fragmentos anidados cuando el fragmento principal se elimina / reemplaza en una transacción, puede "simular" esos fragmentos que aún están presentes al proporcionar una imagen de ellos, tal como aparecieron en la pantalla. Esta imagen se utilizará como fondo para el contenedor de fragmentos anidados, por lo que incluso si las vistas del fragmento anidado desaparecen, la imagen simulará su presencia. Además, no veo la posibilidad de perder la interactividad con las vistas del fragmento anidado como un problema porque no creo que desees que el usuario actúe sobre ellas cuando están en proceso de eliminación (probablemente como acción del usuario como bien).
He hecho un pequeño ejemplo con la configuración de la imagen de fondo (algo básico).
Parece que hay muchas soluciones diferentes para esto, pero basado en la respuesta de @ Jayd16, creo que encontré una solución atrapante bastante sólida que todavía permite animaciones de transición personalizadas en fragmentos secundarios, y no requiere hacer un caché de mapa de bits del diseño.
Tenga una clase BaseFragment
que amplíe Fragment
y haga que todos sus fragmentos amplíen esa clase (no solo fragmentos secundarios).
En esa clase BaseFragment
, agregue lo siguiente:
// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
final Fragment parent = getParentFragment();
// Apply the workaround only if this is a child fragment, and the parent
// is being removed.
if (!enter && parent != null && parent.isRemoving()) {
// This is a workaround for the bug where child fragments disappear when
// the parent is removed (as all children are first removed from the parent)
// See https://code.google.com/p/android/issues/detail?id=55228
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else {
return super.onCreateAnimation(transit, enter, nextAnim);
}
}
private static long getNextAnimationDuration(Fragment fragment, long defValue) {
try {
// Attempt to get the resource ID of the next animation that
// will be applied to the given fragment.
Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
nextAnimField.setAccessible(true);
int nextAnimResource = nextAnimField.getInt(fragment);
Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);
// ...and if it can be loaded, return that animation''s duration
return (nextAnim == null) ? defValue : nextAnim.getDuration();
} catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
Log.w(TAG, "Unable to load next animation from parent.", ex);
return defValue;
}
}
Desgraciadamente, requiere reflexión; sin embargo, dado que esta solución alternativa es para la biblioteca de soporte, no corre el riesgo de que la implementación subyacente cambie a menos que actualice su biblioteca de soporte. Si está creando la biblioteca de soporte desde el origen, puede agregar un descriptor de acceso para el siguiente ID de recurso de animación a Fragment.java
y eliminar la necesidad de reflexión.
Esta solución elimina la necesidad de "adivinar" la duración de la animación principal (para que la animación "no hacer nada" tenga la misma duración que la animación de salida del padre), y le permite hacer animaciones personalizadas en fragmentos secundarios (por ejemplo, si re intercambiando fragmentos de niños con diferentes animaciones).
Pude encontrar una solución bastante limpia. IMO es el menos hacky, y aunque técnicamente es la solución "dibujar un mapa de bits", al menos es abstraída por el fragmento lib.
Asegúrese de que su hijo falle y anule una clase para padres con esto:
private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
dummyAnimation.setDuration(500);
}
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if(!enter && getParentFragment() != null){
return dummyAnimation;
}
return super.onCreateAnimation(transit, enter, nextAnim);
}
Si tenemos una animación de salida en los fragmentos de elementos secundarios, se animarán en lugar de parpadear. Podemos explotar esto teniendo una animación que simplemente dibuja los fragmentos del niño en alfa completo durante un tiempo. De esta manera, se mantendrán visibles en el fragmento principal a medida que se va animando, dando el comportamiento deseado.
El único problema en el que puedo pensar es hacer un seguimiento de esa duración. Tal vez podría establecerlo en un número grande, pero me temo que podría haber problemas de rendimiento si todavía está dibujando esa animación en alguna parte.
Una forma sencilla de solucionar este problema es usar la clase Fragment
de esta biblioteca en lugar de la clase de fragmento de biblioteca estándar:
https://github.com/marksalpeter/contract-fragment
Como nota al margen, el paquete también contiene un patrón de delegado útil llamado ContractFragment
que puede resultarle útil para crear sus aplicaciones aprovechando la relación de fragmentos padre-hijo.
puedes hacer esto en el fragmento de niño.
@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
if (true) {//condition
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
objectAnimator.setDuration(333);//time same with parent fragment''s animation
return objectAnimator;
}
return super.onCreateAnimator(transit, enter, nextAnim);
}