android - studio - Cómo determinar cuándo el fragmento se hace visible en ViewPager
slide android studio (21)
Problema: el fragmento onResume()
en ViewPager
se ViewPager
antes de que el fragmento sea realmente visible.
Por ejemplo, tengo 2 fragmentos con ViewPager
y FragmentPagerAdapter
. El segundo fragmento solo está disponible para usuarios autorizados y debo pedirle al usuario que inicie sesión cuando el fragmento esté visible (mediante un cuadro de diálogo de alerta).
PERO el ViewPager
crea el segundo fragmento cuando el primero es visible para almacenar el segundo fragmento y lo hace visible cuando el usuario comienza a deslizar.
Por lo tanto, el evento onResume()
en el segundo fragmento mucho antes de que sea visible. Es por eso que estoy tratando de encontrar un evento que se active cuando el segundo fragmento se haga visible para mostrar un diálogo en el momento adecuado.
¿Cómo se puede hacer esto?
Cómo determinar cuándo el fragmento se hace visible en ViewPager
Puede hacer lo siguiente anulando setUserVisibleHint
en su Fragment
:
public class MyFragment extends Fragment {
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
}
else {
}
}
}
Agregue el siguiente código dentro del fragmento
@Override
public void setMenuVisibility(final boolean visible)
{
super.setMenuVisibility(visible);
if (visible && isResumed())
{
}
}
Anulé el método de recuento del FragmentStatePagerAdapter asociado y hago que devuelva el recuento total menos el número de páginas para ocultar:
public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
{
private List<Fragment> _fragments;
public int TrimmedPages { get; set; }
public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }
public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
{
_fragments = fragments;
TrimmedPages = 0;
}
public override int Count
{
//get { return _fragments.Count; }
get { return _fragments.Count - TrimmedPages; }
}
}
Por lo tanto, si hay 3 fragmentos agregados inicialmente al ViewPager y solo se deben mostrar los primeros 2 hasta que se cumpla alguna condición, anule el recuento de páginas configurando TrimmedPages en 1 y solo debería mostrar las dos primeras páginas.
Esto funciona bien para las páginas al final, pero en realidad no ayuda para las que están en el principio o en el medio (aunque hay muchas formas de hacerlo).
Aquí hay otra forma de usar onPageChangeListener
:
ViewPager pager = (ViewPager) findByViewId(R.id.viewpager);
FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager);
pager.setAdapter(adapter);
pager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageSelected(int pageNumber) {
// Just define a callback method in your fragment and call it like this!
adapter.getItem(pageNumber).imVisible();
}
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
Encontré el mismo problema mientras trabajaba con FragmentStatePagerAdapters
y 3 pestañas. Tenía que mostrar un Dilaog cada vez que se hacía clic en la primera pestaña y ocultarlo haciendo clic en otras pestañas.
La setUserVisibleHint()
solo no ayudó a encontrar el fragmento visible actual.
Al hacer clic desde la tercera pestaña -----> 1ª pestaña. Se disparó dos veces para el segundo fragmento y para el primer fragmento. Lo combiné con el método isResumed ().
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isVisible = isVisibleToUser;
// Make sure that fragment is currently visible
if (!isVisible && isResumed()) {
// Call code when Fragment not visible
} else if (isVisible && isResumed()) {
// Call code when Fragment becomes visible.
}
}
Encontré este problema cuando intentaba que se disparara un temporizador cuando el fragmento en el viewpager estaba en pantalla para que el usuario lo viera.
El temporizador siempre comenzó justo antes de que el usuario viera el fragmento. Esto se debe a que se onResume()
método onResume()
en el fragmento antes de que podamos ver el fragmento.
Mi solución fue hacer una verificación en el método onResume()
. Quería llamar a cierto método ''foo ()'' cuando el fragmento 8 era el fragmento actual de los buscapersonas.
@Override
public void onResume() {
super.onResume();
if(viewPager.getCurrentItem() == 8){
foo();
//Your code here. Executed when fragment is seen by user.
}
}
Espero que esto ayude. He visto que este problema aparece mucho. Esta parece ser la solución más simple que he visto. Muchos otros no son compatibles con API más bajas, etc.
Esto parece restablecer el onResume()
normal onResume()
que cabría esperar. Juega bien al presionar la tecla de inicio para salir de la aplicación y luego volver a ingresar a la aplicación. onResume()
no se llama dos veces seguidas.
@Override
public void setUserVisibleHint(boolean visible)
{
super.setUserVisibleHint(visible);
if (visible && isResumed())
{
//Only manually call onResume if fragment is already visible
//Otherwise allow natural fragment lifecycle to call onResume
onResume();
}
}
@Override
public void onResume()
{
super.onResume();
if (!getUserVisibleHint())
{
return;
}
//INSERT CUSTOM CODE HERE
}
Intenta esto, es un trabajo para mí:
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
}else
{}
}
Me di cuenta de que los métodos onCreateOptionsMenu
y onPrepareOptionsMenu
llamaban solo en el caso del fragmento realmente visible. No pude encontrar ningún método que se comporte como estos, también probé OnPageChangeListener
pero no funcionó para las situaciones, por ejemplo, necesito una variable inicializada en el método onCreate
.
Por lo tanto, estos dos métodos se pueden usar para este problema como solución alternativa, específicamente para trabajos pequeños y cortos.
Creo que esta es la mejor solución pero no la mejor. Usaré esto pero esperaré una mejor solución al mismo tiempo.
Saludos.
Otra solución publicada aquí sobrepasando setPrimaryItem en el pageradapter por kris larson casi me funcionó. Pero este método se llama varias veces para cada configuración. También obtuve NPE de vistas, etc. en el fragmento, ya que no está listo las primeras veces que se llama a este método. Con los siguientes cambios esto funcionó para mí:
private int mCurrentPosition = -1;
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == mCurrentPosition) {
return;
}
if (object instanceof MyWhizBangFragment) {
MyWhizBangFragment fragment = (MyWhizBangFragment) object;
if (fragment.isResumed()) {
mCurrentPosition = position;
fragment.doTheThingYouNeedToDoOnBecomingVisible();
}
}
}
Para detectar Fragment
visibles en ViewPager
, estoy bastante seguro de que usar setUserVisibleHint
no es suficiente.
Aquí está mi solución para ver el fragmento de cheque visible, invisible cuando inicie el viewpager por primera vez, cambie de página, vaya a otra actividad / fragmento / fondo / primer plano`
public class BaseFragmentHelpLoadDataWhenVisible extends Fragment {
protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that
/**
* This method will call when viewpager create fragment and when we go to this fragment from
* background or another activity, fragment
* NOT call when we switch between each page in ViewPager
*/
@Override
public void onStart() {
super.onStart();
if (mIsVisibleToUser) {
onVisible();
}
}
@Override
public void onStop() {
super.onStop();
if (mIsVisibleToUser) {
onInVisible();
}
}
/**
* This method will call at first time viewpager created and when we switch between each page
* NOT called when we go to background or another activity (fragment) when we go back
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
if (isResumed()) { // fragment have created
if (mIsVisibleToUser) {
onVisible();
} else {
onInVisible();
}
}
}
public void onVisible() {
Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show();
}
public void onInVisible() {
Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show();
}
}
EXPLICACIÓN Puede revisar el logcat a continuación cuidadosamente, entonces creo que puede saber por qué esta solución funcionará.
Primer lanzamiento
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception
Fragment1: onCreateView
Fragment1: onStart mIsVisibleToUser=true
Fragment2: onCreateView
Fragment3: onCreateView
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=false
Ir a la página 2
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true
Ir a la página 3
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true
Ir al fondo:
Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true
Ir al primer plano
Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true
Espero te ayude
Reemplace Fragment.onHiddenChanged()
para eso.
public void onHiddenChanged(boolean hidden)
Se llama cuando el estado oculto (como lo devuelve
isHidden()
) del fragmento ha cambiado. Los fragmentos comienzan no ocultos; esto será llamado siempre que el fragmento cambie de estado a partir de eso.Parámetros
hidden
-boolean
: True si el fragmento ahora está oculto, false si no está visible.
Reemplace setPrimaryItem()
en la subclase FragmentPagerAdapter
. Yo uso este método, y funciona bien.
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
// This is what calls setMenuVisibility() on the fragments
super.setPrimaryItem(container, position, object);
if (object instanceof MyWhizBangFragment) {
MyWhizBangFragment fragment = (MyWhizBangFragment) object;
fragment.doTheThingYouNeedToDoOnBecomingVisible();
}
}
Tenemos un caso especial con MVP en el que el fragmento debe notificar al presentador que la vista se ha vuelto visible, y Dagger inyecta al presentador en el fragment.onAttach()
.
setUserVisibleHint()
no es suficiente, hemos detectado 3 casos diferentes que debían abordarse (se menciona onAttach()
para que sepa cuándo está disponible el presentador):
Fragmento acaba de ser creado. El sistema realiza las siguientes llamadas:
setUserVisibleHint() // before fragment''s lifecycle calls, so presenter is null onAttach() ... onResume()
Fragmento ya creado y botón de inicio presionado. Al restaurar la aplicación a primer plano, esto se llama:
onResume()
Cambio de orientación:
onAttach() // presenter available onResume() setUserVisibleHint()
Solo queremos que la pista de visibilidad llegue al presentador una vez, así es como lo hacemos:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_list, container, false);
setHasOptionsMenu(true);
if (savedInstanceState != null) {
lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION,
getResources().getConfiguration().orientation);
} else {
lastOrientation = getResources().getConfiguration().orientation;
}
return root;
}
@Override
public void onResume() {
super.onResume();
presenter.onResume();
int orientation = getResources().getConfiguration().orientation;
if (orientation == lastOrientation) {
if (getUserVisibleHint()) {
presenter.onViewBecomesVisible();
}
}
lastOrientation = orientation;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (presenter != null && isResumed() && isVisibleToUser) {
presenter.onViewBecomesVisible();
}
}
@Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_LAST_ORIENTATION, lastOrientation);
}
Tenga en cuenta que no se llama a setUserVisibleHint(false)
en la actividad / detención de fragmentos. Todavía tendrá que marcar Iniciar / Detener para register/unregister
correctamente el register/unregister
cualquier oyente / etc.
Además, obtendrás setUserVisibleHint(false)
si tu fragmento comienza en un estado no visible; no desea unregister
allí ya que nunca se ha registrado antes en ese caso.
@Override
public void onStart() {
super.onStart();
if (getUserVisibleHint()) {
// register
}
}
@Override
public void onStop() {
if (getUserVisibleHint()) {
// unregister
}
super.onStop();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) {
// register
if (!mHasBeenVisible) {
mHasBeenVisible = true;
}
} else if (mHasBeenVisible){
// unregister
}
}
Tuve el mismo problema. ViewPager
ejecuta otros eventos del ciclo de vida de fragmentos y no pude cambiar ese comportamiento. Escribí un paginador simple usando fragmentos y animaciones disponibles. SimplePager
Utilicé esto y funcionó!
mContext.getWindow().getDecorView().isShown() //boolean
setUserVisibleHint()
recibe llamadas algunas veces antes de onCreateView()
y otras veces lo que causa problemas.
Para superar esto, debe verificar isResumed()
y también dentro del método setUserVisibleHint()
. Pero en este caso, me di cuenta de que se setUserVisibleHint()
solo si el fragmento se reanudaba y era visible, NO cuando se creaba.
Entonces, si desea actualizar algo cuando Fragmento está visible
, ponga su función de actualización en onCreate()
y setUserVisibleHint()
:
@Override
public View onCreateView(...){
...
myUIUpdate();
...
}
....
@Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible && isResumed()){
myUIUpdate();
}
}
ACTUALIZACIÓN: Todavía me di cuenta de que myUIUpdate()
recibe llamadas dos veces, la razón es que, si tiene 3 pestañas y este código está en la 2ª pestaña, cuando abre la 1ª pestaña por primera vez, la 2ª pestaña también se crea aunque no esté visible y myUIUpdate()
se llama. Luego, cuando myUIUpdate()
a la segunda pestaña, se myUIUpdate()
desde if (visible && isResumed())
y, como resultado, se puede llamar a myUIUpdate()
dos veces en un segundo.
El otro problema es !visible
en setUserVisibleHint
recibe el nombre de 1) cuando se sale de la pantalla de fragmentos y 2) antes de que se cree, cuando se cambia a la pantalla de fragmentos por primera vez.
Solución:
private boolean fragmentResume=false;
private boolean fragmentVisible=false;
private boolean fragmentOnCreated=false;
...
@Override
public View onCreateView(...){
...
//Initialize variables
if (!fragmentResume && fragmentVisible){ //only when first time fragment is created
myUIUpdate();
}
...
}
@Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible && isResumed()){ // only at fragment screen is resumed
fragmentResume=true;
fragmentVisible=false;
fragmentOnCreated=true;
myUIUpdate();
}else if (visible){ // only at fragment onCreated
fragmentResume=false;
fragmentVisible=true;
fragmentOnCreated=true;
}
else if(!visible && fragmentOnCreated){// only when you go out of fragment screen
fragmentVisible=false;
fragmentResume=false;
}
}
Explicación:
fragmentResume
, fragmentVisible
: asegura que se myUIUpdate()
en onCreateView()
solo cuando el fragmento se crea y está visible, no en el currículum. También resuelve el problema cuando estás en la primera pestaña, la segunda pestaña se crea incluso si no está visible. Esto resuelve eso y comprueba si la pantalla de fragmentos está visible cuando onCreate
.
fragmentOnCreated
: Asegura que el fragmento no esté visible y no se llame cuando cree el fragmento por primera vez. Así que ahora esta cláusula if solo es llamada cuando se desliza fuera del fragmento.
Actualización Puede poner todo este código en el código de BaseFragment
esta manera y anular el método.
¡Detectando por focused view
!
Esto funciona para mi
public static boolean isFragmentVisible(Fragment fragment) {
Activity activity = fragment.getActivity();
View focusedView = fragment.getView().findFocus();
return activity != null
&& focusedView != null
&& focusedView == activity.getWindow().getDecorView().findFocus();
}
ACTUALIZACIÓN : la biblioteca de soporte de Android (versión 11) finalmente solucionó el problema de las pistas visibles para el usuario , ahora si usa la biblioteca de soporte para fragmentos, entonces puede usar con seguridad getUserVisibleHint()
o anular setUserVisibleHint()
para capturar los cambios como lo describe la respuesta de gorn.
ACTUALIZACIÓN 1 Aquí hay un pequeño problema con getUserVisibleHint()
. Este valor es por defecto true
.
// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;
Por lo tanto, puede haber un problema cuando intenta usarlo antes de que se setUserVisibleHint()
. Como solución alternativa, puede establecer un valor en el método onCreate
como este.
public void onCreate(@Nullable Bundle savedInstanceState) {
setUserVisibleHint(false);
La respuesta obsoleta:
En la mayoría de los casos de uso, ViewPager
solo muestra una página a la vez, pero los fragmentos almacenados previamente en caché también se ponen en estado "visible" (en realidad invisible) si está utilizando FragmentStatePagerAdapter
en la Android Support Library pre-r11
.
Yo anulo
public class MyFragment extends Fragment {
@Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
// ...
}
}
// ...
}
Para capturar el estado de enfoque del fragmento, creo que es el estado más adecuado de la "visibilidad" que quiere decir, ya que solo un fragmento en ViewPager puede colocar sus elementos de menú junto con los elementos de la actividad principal.
package com.example.com.ui.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.com.R;
public class SubscribeFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_subscribe, container, false);
return view;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// called here
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}