Condición de carrera al mostrar y ocultar FAB con AnimationUtils
En una aplicación de Android que permite acceder a los usuarios a través de las redes sociales, muestro y oculto un FAB
utilizando el siguiente código:
- Ocultar FAB en NestedScrollView al desplazarse
- El botón flotante no funciona en la versión Pre-lollipop
- Botón de acción flotante que no se muestra sobre la vista de reciclaje (que se encuentra dentro de un DrawerLayout)
- El margen predeterminado de FloatingActionButton no funciona en lollipop
- Android setBackgroundTintList en los dispositivos pre-lollipop
public abstract class LoginFragment extends Fragment { private FloatingActionButton mFab; private Animation mShowFab; private Animation mHideFab; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mShowFab = AnimationUtils.makeInAnimation(getContext(), false); mShowFab.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mFab.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } }); mHideFab = AnimationUtils.makeOutAnimation(getContext(), true); mHideFab.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mFab.setVisibility(View.INVISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } }); } private void showFab(boolean show) { boolean visible = mFab.isShown(); if (show && !visible) { mFab.startAnimation(mShowFab); } else if (!show && visible) { mFab.startAnimation(mHideFab); } }
Esto funciona bien, cuando llamo el método showFab
anterior showFab
suficientemente lento.
Antes de iniciar cualquier animación, compruebo la visibilidad actual de FloatingActionButton
, de modo que la animación se reproduzca solo una vez , incluso si llamo por ejemplo showFab(true)
varias veces en una fila.
Mi problema:
Cuando se muestra un LoginFragment
en mi aplicación, primero envío una solicitud a un ServiceIntent
para obtener datos de usuario de SQLite y llamar al método siguiente para establecer mi interfaz de usuario en un estado de "espera":
private void setBusy(boolean busy) { mProgressBar.setVisibility(busy ? View.VISIBLE : View.INVISIBLE); showFab(!busy); }
Casi inmediatamente una respuesta de SQLite vuelve – vía un LocalBroadcastManager
y llamo el método antedicho otra vez: setBusy(false)
.
Y entonces el error ocurre y el FAB
no es visible.
Si reemplazo el método FAB
por código sin animación todo funciona bien:
private void showFab(boolean show) { mFab.setVisibility(show ? View.VISIBLE : View.INVISIBLE); }
Pero con la animación – una condición de carreras parece ocurrir.
Como solución, he intentado cancelar ambas animaciones, pero esto no ayuda:
private void showFab(boolean show) { mShowFab.cancel(); mShowFab.reset(); mHideFab.cancel(); mHideFab.reset(); boolean visible = mFab.isShown(); if (show && !visible) { mFab.startAnimation(mShowFab); } else if (!show && visible) { mFab.startAnimation(mHideFab); } }
Por favor sugiera lo que podría hacerse aquí.
He avanzado a través de mi aplicación en depurador numerosas veces ya. El setBusy
(y showFab
) se llaman sólo dos veces cuando se muestra el fragmento, pero ambas llamadas ocurren muy rápidamente – y la FAB
no se muestra –
Primer intento:
Segunda carrera:
ACTUALIZAR:
Desafortunadamente, la synchronized
del método no ayuda tampoco: el FAB
permanece oculto:
private synchronized void showFab(boolean show) { mShowFab.cancel(); mShowFab.reset(); mHideFab.cancel(); mHideFab.reset(); boolean visible = mFab.isShown(); if (show && !visible) { mFab.startAnimation(mShowFab); } else if (!show && visible) { mFab.startAnimation(mHideFab); } }
- Al hacer clic en ActionButton flotante en la esquina derecha, se hace clic en el elemento RecyclerView
- Cómo agregar el botón de acción flotante en la parte superior de scrollview
- Botón de acción flotante El color del borde no está cambiando
- Color FAB para el fondo No cambia en los dispositivos de versión de Gingerbread
- Animación FAB con viewpager / tabslider
- Cómo cambiar FloatingActionButton entre pestañas?
- Animación de Fab en clic (zoom in / out)
- Biblioteca de soporte de diseño de Android FAB en un fragmento ViewPager con diseño de coordinador
Cada una de sus animaciones se ejecutan en hilos separados. Esto conduce a una condición de carrera como usted dijo.
Aquí está lo que está pasando: showFab se dispara con true, luego falso.
- El método mShowFab onAnimationStart se ejecuta y establece el FAB en visible.
- El método mHideFab onAnimationStart se ejecuta y no hace nada.
- El método mShowFab onAnimationEnd se ejecuta y no hace nada.
- El método mHideFab onAnimationEnd ejecuta y establece el FAB como invisible.
- Y usted acaba con un FAB invisible que debe ser visible.
Las variables de su segunda ejecución muestran que la animación mShowFab nunca se ejecutará.
Usted debe ser capaz de resolver la condición de carrera escuchando para el final de la animación de ocultar. Algo como esto:
private void showFab(boolean show) { if (show) { // if you have an animation currently running and you want to show the fab if (mFab.getAnimation() != null && !mFab.getAnimation().hasEnded()) { // then wait for it to complete and begin the next one mFab.getAnimation().setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mFab.setVisibility(View.INVISIBLE); mFab.startAnimation(mShowFab); } @Override public void onAnimationRepeat(Animation animation) { } }); } else { mFab.startAnimation(mShowFab); } } else { mFab.startAnimation(mHideFab); } }
La primera cosa a señalar es que no hay nada como la condición de carreras. La terminología correcta es sólo condición de carrera . Tenga esto en cuenta para el futuro 😉
Supongo que está utilizando un BroadcastReceiver para recibir mensajes y que ha creado el objeto y decirle que se ejecute en un hilo separado debido a la animación. Esto significa que su método showFab puede ser llamado dos veces en una sola vez. Para manejar esto, defina el método como sincronizado .
Aquí está mi propia solución –
En primer lugar, el uso de synchronized
no ayuda aquí, ya que las animaciones se ejecutan en el mismo subproceso.
Mi solución ha sido introducir una variable boolean
separada para mantener el estado FAB final deseado (visible o no visible):
private FloatingActionButton mFab; private boolean mFabVisible; private Animation mShowFab; private Animation mHideFab; private void showFab(boolean show) { if (show && !mFabVisible) { mFab.startAnimation(mShowFab); } else if (!show && mFabVisible) { mFab.startAnimation(mHideFab); } mFabVisible = show; }
Además, creo que otra posibilidad podría haber sido llamar a mFab.setVisibility(View.VISIBLE)
dos veces – como en el siguiente código. Pero no lo he probado realmente:
mShowFab = AnimationUtils.makeInAnimation(getContext(), false); mShowFab.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mFab.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { mFab.setVisibility(View.VISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } }); mHideFab = AnimationUtils.makeOutAnimation(getContext(), true); mHideFab.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mFab.setVisibility(View.INVISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } });
- Agregar insignia de notificación en el icono de la aplicación en android
- ¿Debería poner la carpeta Ionic Platforms en el control de código fuente?