Cómo determinar cuándo Fragmento se vuelve visible en ViewPager

754

Problema: El fragmento onResume()de ViewPagerse dispara antes de que el fragmento se convierte en realidad visible.

Por ejemplo, tengo 2 fragmentos con ViewPagery FragmentPagerAdapter. El segundo fragmento solo está disponible para usuarios autorizados y necesito pedirle al usuario que inicie sesión cuando el fragmento esté visible (usando un diálogo de alerta).

PERO ViewPagercrea el segundo fragmento cuando el primero es visible para almacenar en caché el segundo fragmento y lo hace visible cuando el usuario comienza a deslizar.

Entonces, el onResume()evento se dispara 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 apropiado.

¿Cómo se puede hacer esto?

4ntoine
fuente
18
"Tengo 2 fragmentos con ViewPager y FragmentPagerAdapter. El segundo fragmento puede estar disponible solo para usuarios autorizados y debo solicitar el uso para iniciar sesión cuando el fragmento se hace visible (diálogo de alerta)". - En mi humilde opinión, eso es horrible UX. Aparecer un cuadro de diálogo porque el usuario deslizó horizontalmente me haría darle una calificación de una estrella en Play Store.
CommonsWare
¿Es mejor mostrar información en TextView con el botón "Iniciar sesión"? ¿Cuál es su solución para ese caso?
4ntoine
55
"No es necesario cargar datos si no se mostrarán". - entonces no deberías ponerlo en un ViewPager. En un buscapersonas de dos páginas, ambas páginas se cargarán inmediatamente, te guste o no. Se ViewPagersupone que la experiencia del usuario es que el contenido está allí inmediatamente al deslizar, no un tiempo después. Es por eso que ViewPagerinicializa una página antes de lo que es visible, para ayudar a garantizar la experiencia del usuario.
CommonsWare
2
Parece que ViewPager no es lo suficientemente flexible y no permite desactivar el almacenamiento en caché ya que setOffscreenPageLimit mínimo es 1: stackoverflow.com/questions/10073214/… . No veo ninguna razón para esto y el comportamiento esperado (en caso de almacenamiento en caché obligatorio) es crear un fragmento PERO disparar el fragmento onResume () cuando el fragmento se hace visible.
4ntoine
1
Un poco tarde, pero para cualquiera que se enfrente al mismo problema, puede probar la biblioteca FragmentViewPager (soy el autor), que se ocupa de este problema y ofrece algunas características adicionales. Para ver una muestra, consulte la página de GitHub del proyecto o esta respuesta de stackoverflow .
S. Brukhanda

Respuestas:

581

Cómo determinar cuándo Fragmento se vuelve visible en ViewPager

Puede hacer lo siguiente anulando setUserVisibleHinten su Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
Gorn
fuente
77
Gracias a la actualización de la Biblioteca de soporte de Android de hoy (rev 11), el problema de sugerencia visible del usuario finalmente se solucionó. Ahora es seguro usar una sugerencia visible del usuario para ViewPager.
Oasis Feng
58
He descubierto que el método setUserVisibleHint se llama ANTES de que se llame a onCreateView y esto dificulta el seguimiento de cualquier inicialización.
AndroidDev
11
@AndroidDev Si desea ejecutar algo de código cuando la sugerencia es verdadera, pero necesita el árbol de vista ya inicializado, simplemente envuelva ese bloque de código isResumed()para evitar un NPE. He estado trabajando bien para mí.
Ravi Thapliyal
13
Es simplemente ridículo que haya tantos hacks diferentes para algo que el SDK debería proporcionar por defecto.
Mike6679
15
setUserVisibleHintahora está en desuso
SR
525

ACTUALIZACIÓN : Android Support Library (rev 11) finalmente solucionó el problema de la sugerencia visible del usuario , ahora si usa la biblioteca de soporte para fragmentos, puede usar getUserVisibleHint()o anular de forma segura setUserVisibleHint()para capturar los cambios como se describe en 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 setUserVisibleHint()se invoque. Como solución alternativa, puede establecer un valor en un onCreatemétodo como este.

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);

La respuesta desactualizada:

En la mayoría de los casos de uso, ViewPagersólo se mostrará una página a la vez, pero los fragmentos pre-caché también se ponen a estado "visible" (en realidad invisible) si está utilizando FragmentStatePagerAdapteren Android Support Library pre-r11.

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, que 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.

Oasis Feng
fuente
73
Tenga cuidado al usar getActivity (), en el primer inicio será nulo.
vovkab
10
Tenga en cuenta que setUserVisibleHint () siempre ha funcionado correctamente en FragmentPagerAdapter.
louielouie
8
Esta solución (y también la de Gorn) está un poco retrasada. En los casos en que el visor se desliza rápidamente y varias veces, este método se llamará con retraso (cuando el fragmento ya no sea visible / invisible).
AsafK
3
Estoy obteniendo truegetUserVisibleHint () cuando onCreateOptionsMenuse llama, y ​​cuando setUserVisibleHintse llama, el menú aún no se ha creado, parece. Finalmente, obtengo dos opciones de Menú agregado cuando solo quiero que el menú sea visible desde el fragmento. ¿Alguna sugerencia al respecto?
cYrixmorten
13
setUserVisibleHintahora está en desuso
SR
143

Esto parece restaurar el onResume()comportamiento normal que cabría esperar. Se juega bien presionando 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
}
craigrs84
fuente
44
Exactamente lo que estaba buscando. Resuelve el problema de setUserVisibleHintser llamado antes onCreateViewy setUserVisibleHintno se llama si la aplicación pasa a segundo plano y luego a primer plano. ¡Increíble! ¡Gracias!
Alex
11
Creo que recurrir a CV es una muy mala idea, pero de lo contrario, ¡todo lo que necesita para responder la pregunta de esta publicación está en la función setUserVisibleHint!
Quentin G.
44
Prefiero implementar un onVisibleToUser()método simple y hacer que se llame desde onResume()y desde en setUserVisibleHint(boolean)lugar de llamarme onResume()e interferir con las devoluciones de llamada del ciclo de vida. De lo contrario, creo que este enfoque funciona bien, ¡gracias!
Stephan Henningsen
1
Sí, estoy de acuerdo con los comentarios anteriores. En general, trate de evitar llamar a métodos públicos desde métodos públicos en su clase. Cree un método privado y llámelo desde ambos métodos públicos.
Johan Franzén
44
setUserVisibleHint en desuso!
Hamid Reza
70

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

  }
});
Ostkontentitan
fuente
1
Esta solución funciona bien, gracias. Esta debería ser la solución recomendada para aquellos que compilan para plataformas más antiguas utilizando los paquetes de soporte de fragmentos (v4)
Frank Yin
77
Vale la pena señalar que esto no podría darle el resultado esperado si está utilizando un FragmentStatePagerAdapter e instanciando una nueva instancia de Fragment en getItem (), porque solo establecería el estado en el nuevo fragmento y no el que obtuvo el visor
Ben Pearson
1
Esta solución es buena si desea realizar algo cuando el fragmento se hace visible. No mantiene el estado visible de un fragmento (es decir, no sabe cuándo el fragmento es invisible).
AsafK
Soy el mismo tipo de problema. Por favor ayudame a resolver el problema. Mi publicación: stackoverflow.com/questions/23115283/…
Jeeten Parmar
esto está dando una excepción de puntero nulo para el adaptador que estoy usando en el método iamVisible del fragmento. Estoy tratando de configurar los datos recibidos de la red solo cuando el fragmento es visible.
zaphod100.10
58

setUserVisibleHint()se llama a veces antes onCreateView() y a veces después de lo cual causa problemas.

Para superar esto, debe verificar isResumed()también el setUserVisibleHint()método interno . Pero en este caso, me di cuenta de que solosetUserVisibleHint() se llama si Fragment se reanuda y es visible, NO cuando se crea.

Entonces, si desea actualizar algo cuando Fragment es visible, coloque 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 a myUIUpdate()veces se llama dos veces, la razón es que si tiene 3 pestañas y este código está en la segunda pestaña, cuando abre la primera pestaña, la segunda pestaña también se crea, incluso si no es visible y myUIUpdate()se llama. Luego, cuando desliza a la segunda pestaña, myUIUpdate()desde if (visible && isResumed())se llama y, como resultado, myUIUpdate()puede llamarse dos veces en un segundo.

El otro problema es !visibleen setUserVisibleHintobtiene la llamada tanto 1) cuando sale de la pantalla y el fragmento 2) antes de que se crea, cuando se cambia a la pantalla fragmento 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: Se asegura myUIUpdate()de que onCreateView()se invoca solo cuando se crea un fragmento y está visible, no en el currículum. También resuelve el problema cuando está 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 es visible cuando onCreate.

fragmentOnCreated: Se asegura de que el fragmento no sea visible y no se llame cuando se crea un fragmento por primera vez. Así que ahora esta cláusula if solo se llama cuando deslizas el fragmento.

Actualización Puede poner todo este código en un BaseFragmentcódigo como este y anular el método.

Jemshit Iskenderov
fuente
1
Su solución funciona perfectamente, excepto en un escenario, cuando el fragmento se abre normalmente y luego hace clic en algún botón para reemplazarlo por otro fragmento y luego hace clic de nuevo para que el nuevo fragmento salga de la pila, en ese caso setUserVisibleHintno recibió llamada. ! y dentro del onCreateViewmétodo fragmentVisiblefue false! entonces el fragmento apareció vacío ...! Alguna idea.?
Alaa AbuZarifa
28

Para detectar Fragmenten ViewPagervisible, estoy bastante seguro de que solo usar setUserVisibleHint no es suficiente.
Aquí está mi solución para verificar si un fragmento es visible o invisible. Primero, cuando inicie el visor, 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 be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
     * NOT called 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 called 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 verificar cuidadosamente el logcat a continuación, luego creo que puede saber por qué funcionará esta solución

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ágina2

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

Proyecto DEMO aquí

Espero que ayude

Phan Van Linh
fuente
1
No funciona cuando un fragmento tiene otro buscapersonas que también consiste en un fragmento.
Shihab Uddin
lo siento, no puedo publicar la respuesta en este momento porque no tengo suficiente tiempo, si es posible, consulte mi mensaje sobre su problema aquí github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmentse utiliza para detectar a fragment has another view pager which also consists fragment. Puedes mezclar SubChildContainerFragmentya ChildContainerFragment1 clase. Espero que sea de ayuda. Publicaré la respuesta completa más tarde
Phan Van Linh
26
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);
    }
}
w3hacker
fuente
2
Esta debería ser la respuesta aceptada. Funciona con android.app.Fragment también, que es una gran ventaja.
RajV
setUserVisibleHintEn
desuso
23

En ViewPager2y ViewPagerdesde la versión androidx.fragment:fragment:1.1.0, puede usar onPausey onResumedevoluciones de llamada para determinar qué fragmento es actualmente visible para el usuario. onResumeSe llama a la devolución de llamada cuando el fragmento se hizo visible y onPausecuando deja de ser visible.

En el caso de ViewPager2, es un comportamiento predeterminado, pero el mismo comportamiento se puede habilitar para el bien antiguo ViewPagerfácilmente.

Para habilitar este comportamiento en el primer ViewPager, debe pasar el FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparámetro como segundo argumento del FragmentPagerAdapterconstructor.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Nota: el setUserVisibleHint()método y el FragmentPagerAdapterconstructor con un parámetro ahora están en desuso en la nueva versión de Fragment de android jetpack.

lukjar
fuente
1
Gracias por la buena solución. Estaba buscando esto desde hace mucho tiempo. Gran trabajo
Anurag Srivastava
1
Para ViewPager2, la opción BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT no es el comportamiento predeterminado, debe configurarlo manualmente. Al configurar esta opción, su solución funcionó para mí. Gracias.
DRIAN
1
A partir de APR 2020, esta es la solución actualizada y funciona de maravilla.
Prakash
1
@ 4ntoine, considere aceptar esta respuesta como la respuesta correcta, ya que a partir de ahora es correcta y la mayoría de las respuestas están utilizando el método setUserVisibleHint en desuso
Jorn Rigter
16

Anulación setPrimaryItem()en la FragmentPagerAdaptersubclase. 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();
    }
}
kris larson
fuente
1
Esto casi funcionó, pero tuvo problemas con NPE y fue llamado varias veces. Publicado una alternativa a continuación!
Gober
@Gober guardando el objeto Object por primera vez, y verifica e ignora la llamada de seguimiento con el mismo objeto Object tal como lo hace FragmentStateAdapter en el método setPrimaryItem ()
wanglugao
12

Anular Fragment.onHiddenChanged()por eso.

public void onHiddenChanged(boolean hidden)

Se invoca cuando el estado oculto (tal como lo devuelve isHidden()) del fragmento ha cambiado. Los fragmentos comienzan no ocultos; Esto se llamará siempre que el fragmento cambie de estado.

Parámetros
hidden- boolean: True si el fragmento está escondida, falso si no es visible.

dan
fuente
3
este método está en desuso por ahora y ya no se puede usar
mientras que el
44
@bluewhile, ¿dónde ves que está en desuso? developer.android.com/reference/android/app/…
Cel
1
Acabo de usar esto con éxito, y la documentación no parece indicar que está en desuso.
Trevor
1
@bluewhile, en 4.1 (api 16) todavía no está en desuso.
dan
8
Estoy usando API nivel 19 y puedo confirmar que si bien esto no está en desuso, no funciona como se anuncia. onHiddenChanged no se llama cuando el fragmento está oculto por otro fragmento, por ejemplo.
4

Me di cuenta de eso onCreateOptionsMenuy los onPrepareOptionsMenumétodos llamados solo en el caso del fragmento realmente visible. No pude encontrar ningún método que se comportara así, también lo intenté OnPageChangeListenerpero no funcionó para las situaciones, por ejemplo, necesito una variable inicializada enonCreate método.

Por lo tanto, estos dos métodos se pueden usar para este problema como una 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 espero una mejor solución al mismo tiempo.

Saludos.

ismailarilik
fuente
2

Otra solución publicada aquí anulando 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 esto 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();
        }
    }
}
Gober
fuente
2

Agregue el siguiente código dentro del fragmento

@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}
Afzaal Iftikhar
fuente
Esto solo funciona para ViewPagerFragments. No para fragmentos en actividad normal.
AndroidGuy
2

Encontré el mismo problema mientras trabajaba con FragmentStatePagerAdapters3 pestañas. Tenía que mostrar un Dilaog cada vez que se hacía clic en la primera pestaña y ocultarlo al hacer clic en otras pestañas.

Anular setUserVisibleHint()solo no ayudó a encontrar el fragmento visible actual.

Al hacer clic desde la tercera pestaña -----> primera pestaña. Se activó 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.
    }

}
Aman Arora
fuente
2

Tenemos un caso especial con MVP donde el fragmento debe notificar al presentador que la vista se ha vuelto visible, y Dagger inyecta al presentador fragment.onAttach().

setUserVisibleHint()no es suficiente, hemos detectado 3 casos diferentes que debían abordarse ( onAttach()se menciona para que sepa cuándo está disponible el presentador):

  1. Fragmento acaba de ser creado. El sistema realiza las siguientes llamadas:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. Fragmento ya creado y se presiona el botón de inicio. Al restaurar la aplicación a primer plano, esto se llama:

    onResume()
  3. Cambio de orientación:

    onAttach() // presenter available
    onResume()
    setUserVisibleHint()

Solo queremos que la pista de visibilidad llegue al presentador una vez, así que 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);
}
Alfiler
fuente
2

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();
}
Mohammad Reza Norouzi
fuente
1

Encontré este problema cuando intentaba que se disparara un temporizador cuando el fragmento en el visor estaba en la pantalla para que el usuario lo viera.

El temporizador siempre comenzó justo antes de que el usuario viera el fragmento. Esto se debe onResume()a que se llama al método en el fragmento antes de que podamos ver el fragmento.

Mi solución fue verificar el onResume()método. 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 surgir mucho este problema. Esta parece ser la solución más simple que he visto. Muchos otros no son compatibles con API inferiores, etc.

Malone
fuente
1

Tuve el mismo problema. ViewPagerejecuta otros fragmentos de eventos del ciclo de vida y no pude cambiar ese comportamiento. Escribí un buscapersonas simple usando fragmentos y animaciones disponibles. SimplePager

Rukmal Dias
fuente
1

¡Usé esto y funcionó!

mContext.getWindow().getDecorView().isShown() //boolean
Ali Akram
fuente
1

Apoyo SectionsPagerAdapter con fragmentos secundarios, así que después de mucho dolor de cabeza finalmente obtuve una versión funcional basada en soluciones de este tema:

public abstract class BaseFragment extends Fragment {

    private boolean visible;
    private boolean visibilityHintChanged;

    /**
     * Called when the visibility of the fragment changed
     */
    protected void onVisibilityChanged(View view, boolean visible) {

    }

    private void triggerVisibilityChangedIfNeeded(boolean visible) {
        if (this.visible == visible || getActivity() == null || getView() == null) {
            return;
        }
        this.visible = visible;
        onVisibilityChanged(getView(), visible);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!visibilityHintChanged) {
            setUserVisibleHint(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        triggerVisibilityChangedIfNeeded(!hidden);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        visibilityHintChanged = true;
        if (isVisibleToUser && isResumed() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        } else if (!isVisibleToUser) {
            triggerVisibilityChangedIfNeeded(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        triggerVisibilityChangedIfNeeded(false);
    }

    @Override
    public void onStop() {
        super.onStop();
        triggerVisibilityChangedIfNeeded(false);
    }

    protected boolean isReallyVisible() {
        return visible;
    }
}
Andoctorey
fuente
Olvidé mencionar que una vez que agrega un fragmento secundario al conjunto primario, fragment.setUserVisibleHint (true);
Andoctorey
Y no olvide usar getChildFragmentManager () en lugar de getFragmentManager () para agregar fragmentos secundarios.
Andoctorey
0

Tenga en cuenta que setUserVisibleHint(false)no se llama en la parada de actividad / fragmento. Aún deberá verificar el inicio / detención pararegister/unregister escuchar los oyentes / etc.

Además, obtendrá setUserVisibleHint(false)si su fragmento comienza en un estado no visible; no quieres unregisterallí ya que nunca te has 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
    }
}
once cinco
fuente
0

Una forma sencilla de implementar es verificar si el usuario ha iniciado sesión antes de ir al fragmento.

En su MainActivity puede hacer algo como esto dentro del método onNavigationItemSelected .

 case R.id.nav_profile_side:


                if (User_is_logged_in) {

                    fragmentManager.beginTransaction()
                            .replace(R.id.content_frame
                                    , new FragmentProfile())
                            .commit();
                }else {

                    ShowLoginOrRegisterDialog(fragmentManager);

                }

                break;

Sin embargo, si está utilizando el cajón de navegación, la selección en el cajón habrá cambiado a Perfil, aunque no hemos ido al Fragmento de perfil.

Para restablecer la selección a la selección actual, ejecute el siguiente código

        navigationView.getMenu().getItem(0).setChecked(true);
Martin Mbae
fuente
0

setUserVisibleHint (boolean visible) ahora está en desuso Así que esta es la solución correcta

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

En ViewPager2 y ViewPager desde la versión androidx.fragment:fragment:1.1.0, solo puede usar onPause()y onResume()determinar qué fragmento es actualmente visible para el usuario. onResume()Se llama cuando el fragmento se hizo visible y onPausecuando deja de ser visible.

Para habilitar este comportamiento en el primer ViewPager, debe pasar el FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparámetro como segundo argumento del FragmentPagerAdapterconstructor.

Rahul
fuente
-4

Anulé el método Count del FragmentStatePagerAdapter asociado y pido 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; }
     }
 }

Entonces, si hay 3 fragmentos inicialmente agregados 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 realmente no ayudará para las que están al principio o en el medio (aunque hay muchas maneras de hacerlo).

Sam es
fuente