android - studio - Animación FragmentTransaction para deslizarse hacia arriba
fragments dinamicos android studio (7)
A partir de Lollipop, puede aumentar la traducciónZ de su fragmento que ingresa. Aparecerá arriba de la salida.
Por ejemplo:
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewCompat.setTranslationZ(getView(), 100.f);
}
Si desea modificar el valor de traducciónZ solo durante la duración de la animación, debe hacer algo como esto:
@Override
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim);
nextAnimation.setAnimationListener(new Animation.AnimationListener() {
private float mOldTranslationZ;
@Override
public void onAnimationStart(Animation animation) {
if (getView() != null && enter) {
mOldTranslationZ = ViewCompat.getTranslationZ(getView());
ViewCompat.setTranslationZ(getView(), 100.f);
}
}
@Override
public void onAnimationEnd(Animation animation) {
if (getView() != null && enter) {
ViewCompat.setTranslationZ(getView(), mOldTranslationZ);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
return nextAnimation;
}
Estoy tratando de lograr el siguiente efecto usando FragmentTransaction.setCustomAnimations.
- El fragmento A está mostrando
- Reemplace el Fragmento A con el Fragmento B. El Fragmento A debe permanecer visible durante el reemplazo. El fragmento B debe deslizarse desde la derecha. El fragmento B debe deslizarse por encima del Fragmento A.
No tengo problemas para obtener la diapositiva en la configuración de la animación. Mi problema es que no puedo entender cómo hacer que Fragment A permanezca donde está y ESTAR BAJO Fragmento B mientras se ejecuta la diapositiva de animación. No importa lo que haga, parece que el Fragmento A está arriba.
¿Cómo puedo conseguir esto?
Aquí está el código FragmentTransaction:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing,
R.anim.slide_out_right);
ft.replace(R.id.fragment_content, fragment, name);
ft.addToBackStack(name);
ft.commit();
Como puede ver, he definido una animación R.anim.nothing para la animación "out" porque en realidad no quiero que el Fragmento A haga otra cosa que no sea quedarse donde está durante la transacción.
Aquí están los recursos de animación:
slide_in_right.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%p"
android:toXDelta="0"
android:zAdjustment="top" />
nada.xml
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="1.0"
android:zAdjustment="bottom" />
Después de más experimentación (de ahí que esta sea mi segunda respuesta), el problema parece ser que R.anim.nothing significa ''desaparecer'' cuando queremos otra animación que explícitamente dice ''quédate quieto''. La solución es definir una verdadera animación de "no hacer nada" como esta:
Haga el archivo no_animation.xml
:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/linear_interpolator"
android:fromXScale="1.0"
android:toXScale="1.0"
android:fromYScale="1.0"
android:toYScale="1.0"
android:duration="200"
/>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="200"
android:startOffset="200"
/>
</set>
Ahora simplemente haz lo que harías de otra manera:
getActivity().getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation)
.replace(R.id.container, inFrag, FRAGMENT_TAG)
.addToBackStack("Some text")
.commit();
Encontré una solución alternativa (no muy probada) que me parece más elegante que las propuestas hasta ahora:
final IncomingFragment newFrag = new IncomingFragment();
newFrag.setEnterAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
clearSelection();
inFrag.clearEnterAnimationListener();
getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
getActivity().getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_from_right, 0)
.add(R.id.container, inFrag)
.addToBackStack(null)
.commit();
Esto se llama desde dentro de una clase interna de la clase OutgoingFragment.
Se inserta un nuevo fragmento, se completa la animación y luego se elimina el fragmento anterior.
Puede haber algunos problemas de memoria con esto en algunas aplicaciones, pero es mejor que conservar ambos fragmentos indefinidamente.
Encontré una solución que funciona para mí. Terminé usando ViewPager con FragmentStatePagerAdapter. ViewPager proporciona el comportamiento de deslizamiento y los intercambios FragmentStatePagerAdapter en los fragmentos. El truco final para lograr el efecto de tener una página visible "debajo" de la página entrante es usar un PageTransformer. El PageTransformer anula la transición predeterminada de ViewPager entre páginas. Aquí hay un ejemplo de PageTransformer que logra el efecto con la traducción y una pequeña cantidad de escala en la página del lado izquierdo.
public class ScalePageTransformer implements PageTransformer {
private static final float SCALE_FACTOR = 0.95f;
private final ViewPager mViewPager;
public ScalePageTransformer(ViewPager viewPager) {
this.mViewPager = viewPager;
}
@SuppressLint("NewApi")
@Override
public void transformPage(View page, float position) {
if (position <= 0) {
// apply zoom effect and offset translation only for pages to
// the left
final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR;
int pageWidth = mViewPager.getWidth();
final float translateValue = position * -pageWidth;
page.setScaleX(transformValue);
page.setScaleY(transformValue);
if (translateValue > -pageWidth) {
page.setTranslationX(translateValue);
} else {
page.setTranslationX(0);
}
}
}
}
Esta es mi solución actual para cualquiera que esté interesado.
En la función para agregar el nuevo Fragment
:
final Fragment toRemove = fragmentManager.findFragmentById(containerID);
if (toRemove != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
fragmentManager.beginTransaction().hide(toRemove).commit();
}
},
getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100);
// Use whatever duration you chose for your animation for this handler
// I added an extra 100 ms because the first transaction wasn''t always
// fast enough
}
fragmentManager.beginTransaction()
.setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd)
.addToBackStack(tag).commit();
y en onCreate
:
final FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
Fragment current = fragmentManager.findFragmentById(containerID);
if (current != null && current.isHidden()) {
fragmentManager.beginTransaction().show(current).commit();
}
}
});
Preferiría algún tipo de AnimationListener en lugar de un Handler anterior, pero no vi ninguna manera de usar uno para detectar el final de la animación de transacción que no estaba vinculada al fragmento como onCreateAnimation()
. Cualquier sugerencia / ediciones con un oyente apropiado sería apreciada.
Señalaré que los Fragment
que agrego de esta manera son livianos, así que no es un problema para mí tenerlos en el contenedor de fragmentos junto con el fragmento sobre el que se encuentran.
Si desea eliminar el fragmento, puede poner fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss();
en Handler
''s Runnable
, y en OnBackStackChangedListener
:
// Use back stack entry tag to get the fragment
Fragment current = getCurrentFragment();
if (current != null && !current.isAdded()) {
fragmentManager.beginTransaction()
.add(containerId, current, current.getTag())
.commitNowAllowingStateLoss();
}
Tenga en cuenta que la solución anterior no funciona para el primer fragmento en el contenedor (porque no está en la pila posterior) por lo que tendría que tener otra forma de restaurar esa, tal vez guardar una referencia al primer fragmento de alguna manera .. . Pero si no usa la pila posterior y siempre reemplaza los fragmentos manualmente, esto no es un problema. O bien, podría agregar todos los fragmentos a la pila posterior (incluido el primero) y anular onBackPressed
para asegurarse de que su actividad onBackPressed
en lugar de mostrar una pantalla en blanco cuando solo queda un fragmento en la pila posterior.
EDIT: descubrí las siguientes funciones que probablemente podrían reemplazar FragmentTransaction.remove()
y FragmentTransaction.add()
arriba:
FragmentTransaction
. detach() :
Separar el fragmento dado de la interfaz de usuario. Este es el mismo estado que cuando se coloca en la pila posterior: el fragmento se elimina de la interfaz de usuario, sin embargo, su estado todavía está siendo gestionado activamente por el administrador de fragmentos. Al entrar en este estado, se destruye su jerarquía de vistas.
FragmentTransaction
. attach() :
Vuelva a conectar un fragmento después de haberlo separado previamente de la IU con separación (Fragmento). Esto hace que su jerarquía de vista se vuelva a crear, se adjunte a la interfaz de usuario y se muestre.
No sé si todavía necesitas una respuesta, pero recientemente tuve que hacer lo mismo y encontré la manera de hacer lo que quieres.
Hice algo como esto:
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MyFragment next = getMyFragment();
ft.add(R.id.MyLayout,next);
ft.setCustomAnimations(R.anim.slide_in_right,0);
ft.show(next);
ft.commit();
Expongo mi Fragmento en FrameLayout.
Funciona con multas, pero el Fragmento más antiguo está todavía en mi Vista, dejo que Android lo administre como él quiere porque si pongo:
ft.remove(myolderFrag);
no se muestra durante la animación.
slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="150" android:fromXDelta="100%p"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="0" />
</set>
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(0, R.anim.slide_out_to_right);
if (!fragment.isAdded())
{
ft.add(R.id.fragmentContainerFrameMyOrders, fragment);
ft.show(fragment);
}
else
ft.replace(R.id.fragmentContainerFrameMyOrders, fragment);
ft.commit();