Come determinare quando il frammento diventa visibile in ViewPager


754

Problema: Frammento onResume()in ViewPagerviene generato prima il frammento diventa realtà visibile.

Ad esempio, ho 2 frammenti con ViewPagere FragmentPagerAdapter. Il secondo frammento è disponibile solo per gli utenti autorizzati e devo chiedere all'utente di accedere quando il frammento diventa visibile (usando una finestra di avviso).

MA ViewPagercrea il secondo frammento quando il primo è visibile per memorizzare nella cache il secondo frammento e lo rende visibile quando l'utente inizia a scorrere.

Quindi l' onResume()evento viene generato nel secondo frammento molto prima che diventi visibile. Ecco perché sto cercando di trovare un evento che si attiva quando il secondo frammento diventa visibile per mostrare una finestra di dialogo al momento opportuno.

Come si può fare?


18
"Ho 2 frammento con ViewPager e FragmentPagerAdapter. Il secondo frammento può essere disponibile solo per utenti autorizzati e dovrei chiedere l'uso per accedere quando il frammento diventa visibile (finestra di avviso)." - IMHO, è terribile UX. Aprire una finestra di dialogo perché l'utente scorrendo in orizzontale mi farebbe dare una valutazione a una stella sul Play Store.
Commons War

è meglio visualizzare le informazioni su TextView con il pulsante "Accedi"? Qual è la tua soluzione per quel caso?
4ntoine,

5
"Non è necessario caricare i dati se non verranno visualizzati." - allora non dovresti metterlo in a ViewPager. In un cercapersone di due pagine, entrambe le pagine verranno caricate immediatamente, che ti piaccia o no. L'esperienza dell'utente di ViewPagerdovrebbe essere che il contenuto è lì immediatamente dopo lo scorrimento, non qualche tempo dopo. Questo è il motivo per cui ViewPagerinizializza una pagina prima di ciò che è visibile, per aiutare a garantire l'esperienza dell'utente.
Commons War

2
Sembra che ViewPager non è abbastanza flessibile e non permette di disattivare la cache dal minimo setOffscreenPageLimit è 1: stackoverflow.com/questions/10073214/... . Non vedo alcun motivo per questo e il comportamento previsto (in caso di memorizzazione nella cache obbligatoria) è quello di creare il frammento MA il frammento di fuoco onResume () quando il frammento diventa visibile.
4

1
Un po 'in ritardo, ma per chiunque si trovi ad affrontare lo stesso problema, potresti provare la libreria FragmentViewPager (Sono l'autore), che affronta questo problema e offre alcune funzionalità extra. Per un esempio, controlla la pagina GitHub del progetto o questa risposta StackOverflow .
S. Brukhanda,

Risposte:


581

Come determinare quando il frammento diventa visibile in ViewPager

Puoi fare quanto segue sovrascrivendo il setUserVisibleHinttuo Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}

7
Grazie all'aggiornamento odierno della libreria di supporto Android (rev 11), il problema relativo al suggerimento visibile dell'utente è stato finalmente risolto. Ora è sicuro usare un suggerimento visibile dall'utente per ViewPager.
Oasis Feng,

58
Ho scoperto che il metodo setUserVisibleHint viene chiamato PRIMA che venga chiamato onCreateView e questo rende difficile tenere traccia di qualsiasi inizializzazione.
AndroidDev

11
@AndroidDev Se vuoi eseguire un po 'di codice quando il suggerimento viene inserito come vero, ma che richiede che l'albero della vista sia già inizializzato, avvolgi quel blocco di codice isResumed()per evitare un NPE. Ho lavorato bene per me.
Ravi Thapliyal,

13
È semplicemente ridicolo che ci siano così tanti hack diversi per qualcosa che SDK dovrebbe fornire di default.
Mike6679,

15
setUserVisibleHintè ora obsoleto
SR

525

AGGIORNAMENTO : Android Support Library (rev 11) ha finalmente risolto il problema del suggerimento visibile dell'utente , ora se si utilizza la libreria di supporto per i frammenti, è possibile utilizzare getUserVisibleHint()o sostituire setUserVisibleHint()in modo sicuro per acquisire le modifiche come descritto dalla risposta di Gorn.

AGGIORNAMENTO 1 Ecco un piccolo problema con getUserVisibleHint(). Questo valore è di default true.

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;

Quindi potrebbe esserci un problema quando si tenta di usarlo prima che setUserVisibleHint()fosse invocato. Per ovviare al problema, è possibile impostare il valore in un onCreatemetodo come questo.

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

La risposta obsoleta:

Nella maggior parte dei casi d'uso, ViewPagermostra solo una pagina alla volta, ma anche i frammenti pre-memorizzati nella cache vengono messi nello stato "visibile" (in realtà invisibile) se si utilizza FragmentStatePagerAdapterinAndroid Support Library pre-r11 .

Ho la precedenza:

public class MyFragment extends Fragment {
    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible) {
            // ...
        }
    }
   // ...
}

Per catturare lo stato attivo del frammento, che penso sia lo stato più adatto della "visibilità" intendi, dal momento che solo un frammento in ViewPager può effettivamente posizionare le sue voci di menu insieme alle voci dell'attività principale.


73
Fare attenzione a usare getActivity (), al primo avvio sarà null.
Vovkab,

10
Si noti che setUserVisibleHint () ha sempre funzionato correttamente in FragmentPagerAdapter.
louielouie,

8
Questa soluzione (e anche quella di Gorn) è un po 'in ritardo. Nei casi in cui il viewpager viene spostato rapidamente e più volte, questo metodo verrà chiamato con un ritardo (quando il frammento non è più visibile / invisibile).
AsafK,

3
Sto ottenendo trueper getUserVisibleHint () quando onCreateOptionsMenuviene chiamato, e quando setUserVisibleHintviene chiamato, sembra che il menu non sia stato ancora creato. Alla fine ottengo due opzioni di menu aggiunte quando voglio solo il menu dal frammento che è visibile. Qualche suggerimento al riguardo?
cYrixmorten,

13
setUserVisibleHintè ora obsoleto
SR

143

Questo sembra ripristinare il normale onResume()comportamento che ti aspetteresti. Gioca bene premendo il tasto Home per uscire dall'app e quindi accedere nuovamente all'app. onResume()non viene chiamato due volte di seguito.

@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
}

4
Esattamente quello che stavo cercando. Risolve il problema con la setUserVisibleHintchiamata prima onCreateViewe che setUserVisibleHintnon viene chiamata se l'app passa in background e quindi in primo piano. Eccezionale! Grazie!
Alex,

11
Penso che chiamare onResume te stesso sia una pessima idea, ma per il resto, tutto ciò di cui hai bisogno per rispondere alla domanda di questo post è nella funzione setUserVisibleHint!
Quentin G.

4
Preferirei implementare un onVisibleToUser()metodo semplice e fare in modo che chiamasse da onResume()e verso setUserVisibleHint(boolean)invece di chiamarmi onResume()e interferire con i callback del ciclo di vita. Altrimenti penso che questo approccio funzioni bene, grazie!
Stephan Henningsen,

1
Sì, sono d'accordo con i commenti sopra. In generale, cerca di evitare di chiamare metodi pubblici da metodi pubblici nella tua classe. Crea un metodo privato e chiamalo da entrambi i metodi pubblici.
Johan Franzén,

4
setUserVisibleHint deprecato!
Hamid Reza,

70

Ecco un altro modo di usare 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

  }
});

1
Questa soluzione funziona bene, grazie. Questa dovrebbe essere la soluzione consigliata per coloro che costruiscono piattaforme precedenti usando i pacchetti di supporto per frammenti (v4)
Frank Yin,

7
Vale la pena notare che questo non potrebbe darti il ​​risultato atteso se si utilizza un FragmentStatePagerAdapter e si crea un'istanza di una nuova istanza di Fragment su getItem (), perché si imposterebbe solo lo stato sul nuovo frammento e non quello ottenuto dal viewpager
Ben Pearson,

1
Questa soluzione è buona se vuoi eseguire qualcosa quando il frammento diventa visibile. Non mantiene lo stato visibile di un frammento (significato, non sa quando il frammento è invisibile).
AsafK,

Sono lo stesso tipo di problema. per favore aiutatemi a risolvere il problema. My Post: stackoverflow.com/questions/23115283/...
Jeeten Parmar

questo sta dando un'eccezione puntatore null per l'adattatore che sto usando nel metodo iamVisible del frammento. Sto cercando di impostare i dati ricevuti dalla rete solo quando il frammento è visibile.
zaphod100.10,

58

setUserVisibleHint()viene chiamato a volte prima onCreateView() e talvolta dopo che causa problemi.

Per ovviare a questo è necessario controllare isResumed()anche il setUserVisibleHint()metodo interno . Ma in questo caso ho capito che setUserVisibleHint()viene chiamato solo se il frammento viene ripreso e visibile, NON quando viene creato.

Quindi, se vuoi aggiornare qualcosa quando Fragment è visible, inserisci la tua funzione di aggiornamento sia in onCreate()che setUserVisibleHint():

@Override
public View onCreateView(...){
    ...
    myUIUpdate();
    ...        
}
  ....
@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){
        myUIUpdate();
    }
}

AGGIORNAMENTO: Ancora mi sono reso conto che a myUIUpdate()volte viene chiamato due volte, il motivo è che se hai 3 schede e questo codice è nella seconda scheda, quando apri la prima scheda per la prima volta, viene creata anche la seconda scheda anche se non è visibile e myUIUpdate()viene chiamata. Quindi quando si passa alla seconda scheda, myUIUpdate()da if (visible && isResumed())viene chiamato e, di conseguenza, myUIUpdate()può essere chiamato due volte al secondo.

L'altro problema è !visibleinsetUserVisibleHint viene chiamato sia 1) quando si va fuori dello schermo frammento e 2) prima che venga creato, quando si passa alla schermata frammento prima volta.

Soluzione:

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;
    }
}

Spiegazione:

fragmentResume, fragmentVisible: Verifica che myUIUpdate()in onCreateView()venga chiamato solo quando il frammento viene creato e visibile, non al riavvio. Risolve anche il problema quando si è alla prima scheda, la seconda scheda viene creata anche se non è visibile. Questo risolve e controlla se lo schermo del frammento è visibile quando onCreate.

fragmentOnCreated: Assicura che il frammento non sia visibile e non venga chiamato quando si crea il frammento la prima volta. Quindi ora questa clausola if viene chiamata solo quando si scorre il frammento.

Aggiornamento È possibile inserire tutto questo codice in BaseFragmentcodice come questo e sovrascrivere il metodo.


1
La tua soluzione funziona perfettamente, tranne con uno scenario, quando il frammento viene aperto normalmente e quindi fai clic su un pulsante per sostituirlo con un altro frammento e quindi fai clic indietro per estrarre il nuovo frammento dallo backstack, in quel caso setUserVisibleHintnon è stata chiamata. ! e dentro il onCreateViewmetodo fragmentVisibleera false!? quindi il frammento è apparso vuoto ..! qualche idea.?
Alaa Abu Zarifa,

28

Per rilevare Fragmentin ViewPagervisibile, sono abbastanza sicuro che solo l'uso setUserVisibleHint non è sufficiente.
Ecco la mia soluzione per verificare se un frammento è visibile o invisibile. Innanzitutto quando si avvia viewpager, passare da una pagina all'altra, passare a un'altra attività / frammento / sfondo / primo piano`

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();
    }
}

SPIEGAZIONE Puoi controllare attentamente il logcat di seguito, quindi penso che potresti sapere perché questa soluzione funzionerà

Primo lancio

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

Vai a pagina2

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true

Vai a pagina3

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true

Vai allo sfondo:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true

Vai in primo piano

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true

Progetto DEMO qui

Spero che sia d'aiuto


1
Non funziona quando un frammento ha un altro cercapersone di visualizzazione che comprende anche frammento.
Shihab Uddin,

mi dispiace, non posso pubblicare la risposta in questo momento perché non ho abbastanza tempo, se possibile, si prega di fare riferimento al mio git sul tuo problema qui github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmentè usato per rilevare a fragment has another view pager which also consists fragment. Puoi mescolare SubChildContainerFragmente ChildContainerFragmentin 1 classe. Spero che sia d'aiuto. In seguito pubblicherò la risposta completa
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);
    }
}

2
Questa dovrebbe essere la risposta accettata. Funziona anche con android.app.Fragment, che è un vantaggio enorme.
RajV,

setUserVisibleHintDeprecato
ekashking il

23

Nella ViewPager2e ViewPagerdalla versione androidx.fragment:fragment:1.1.0puoi semplicemente usare onPausee onResumerichiamate per determinare quale frammento è attualmente visibile per l'utente. onResumeil callback viene chiamato quando il frammento diventa visibile e onPausequando si ferma per essere visibile.

Nel caso di ViewPager2 si tratta di un comportamento predefinito, ma lo stesso comportamento può essere abilitato ViewPagerfacilmente per il vecchio bene .

Per abilitare questo comportamento nel primo ViewPager devi passare il FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparametro come secondo argomento del FragmentPagerAdaptercostruttore.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Nota: il setUserVisibleHint()metodo e il FragmentPagerAdaptercostruttore con un parametro sono ora deprecati nella nuova versione di Fragment da Android Jetpack.


1
Grazie per la bella soluzione. Lo stavo cercando da molto tempo. Grande opera
Anurag Srivastava,

1
Per ViewPager2 l'opzione BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT non è il comportamento predefinito, è necessario impostarlo manualmente. Quando si imposta questa opzione, la soluzione ha funzionato per me. Grazie.
Il

1
A partire da aprile 2020 questa è la soluzione aggiornata e funziona come un fascino.
Prakash,

1
@ 4ntoine, per favore, considera di accettare questa risposta come la risposta corretta, dal momento che questo è corretto e la maggior parte delle risposte utilizza il metodo deprecato setUserVisibleHint
Jorn Rigter

16

Sostituisci setPrimaryItem()nella FragmentPagerAdaptersottoclasse. Uso questo metodo e funziona bene.

@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();
    }
}

1
Questo ha funzionato quasi ma ha avuto problemi con NPE ed è stato chiamato più volte. Ha pubblicato un'alternativa di seguito!
Gober,


@Gober salva l'oggetto Object alla prima volta e controlla e ignora la chiamata di follow-up con lo stesso oggetto Object proprio come fa FragmentStateAdapter nel metodo setPrimaryItem ()
wanglugao,

12

Sostituisci Fragment.onHiddenChanged()per quello.

public void onHiddenChanged(boolean hidden)

Chiamato quando lo stato nascosto (come restituito da isHidden()) del frammento è cambiato. I frammenti iniziano non nascosti; questo verrà chiamato ogni volta che il frammento cambia stato da quello.

Parametri
hidden- boolean: True se il frammento è ora nascosto, false se non è visibile.


3
questo metodo è ormai obsoleto e non può più essere utilizzato
bluewhile

4
@bluewhile dove lo vedi è deprecato? developer.android.com/reference/android/app/…
Cel

1
L'ho usato con successo e la documentazione non sembra indicare che sia obsoleta.
Trevor,

1
@bluewhile, in 4.1 (api 16) non è ancora deprecato.
dan

8
Sto usando il livello API 19 e posso confermare che, sebbene questo non sia deprecato, non funziona come pubblicizzato. onHiddenChanged non viene chiamato quando il frammento viene nascosto da un altro frammento, ad esempio.

4

Ho capito che onCreateOptionsMenue onPrepareOptionsMenumetodi chiamati solo nel caso del frammento davvero visibile. Non sono riuscito a trovare alcun metodo che si comporti come questi, inoltre ho provato OnPageChangeListenerma non ha funzionato per le situazioni, ad esempio, ho bisogno di una variabile inizializzata nel onCreatemetodo.

Quindi questi due metodi possono essere utilizzati per questo problema come soluzione alternativa, in particolare per piccoli e brevi lavori.

Penso che questa sia la soluzione migliore ma non la migliore. Userò questo, ma aspetterò una soluzione migliore allo stesso tempo.

Saluti.


2

Un'altra soluzione pubblicata qui che sostituisce setPrimaryItem nel pageradapter di kris larson ha quasi funzionato per me. Ma questo metodo viene chiamato più volte per ogni installazione. Inoltre ho ottenuto NPE da viste, ecc. Nel frammento poiché questo non è pronto le prime volte che viene chiamato questo metodo. Con le seguenti modifiche ha funzionato per me:

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();
        }
    }
}

2

Aggiungi il seguente codice all'interno del frammento

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

     }
}

Funziona solo con ViewPagerFragments. Non per frammenti in attività normale.
Android Acquista

2

Ho riscontrato lo stesso problema mentre lavoravo con FragmentStatePagerAdapterse 3 schede. Ho dovuto mostrare un Dilaog ogni volta che si faceva clic sulla prima scheda e nasconderlo facendo clic su altre schede.

Sostituire da setUserVisibleHint()solo non ha aiutato a trovare l'attuale frammento visibile.

Facendo clic sulla terza scheda -----> 1a scheda. Si è innescato due volte per il 2o frammento e per il 1o frammento. L'ho combinato con il metodo 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.
    }

}

2

Abbiamo un caso speciale con MVP in cui il frammento deve notificare al presentatore che la vista è diventata visibile e il presentatore viene iniettato da Dagger in fragment.onAttach() .

setUserVisibleHint()non è abbastanza, abbiamo rilevato 3 diversi casi che dovevano essere affrontati ( onAttach()è menzionato in modo da sapere quando il presentatore è disponibile):

  1. Il frammento è appena stato creato. Il sistema effettua le seguenti chiamate:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. Frammento già creato e pulsante home premuto. Quando si ripristina l'app in primo piano, questo si chiama:

    onResume()
  3. Cambio di orientamento:

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

Vogliamo che il suggerimento sulla visibilità arrivi al presentatore una sola volta, quindi è così che lo facciamo:

@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);
}

2

Rilevando da focused view!

Questo funziona per me

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();
}

1

Ho riscontrato questo problema quando stavo cercando di far scattare un timer quando il frammento nel viewpager era sullo schermo per essere visibile all'utente.

Il timer si avviava sempre prima che il frammento fosse visibile dall'utente. Questo perché il onResume()metodo nel frammento viene chiamato prima che possiamo vedere il frammento.

La mia soluzione era quella di fare un controllo nel onResume()metodo. Volevo chiamare un certo metodo 'foo ()' quando il frammento 8 era il frammento corrente dei cercapersone della vista.

@Override
public void onResume() {
    super.onResume();
    if(viewPager.getCurrentItem() == 8){
        foo();
        //Your code here. Executed when fragment is seen by user.
    }
}

Spero che sia di aiuto. Ho visto questo problema apparire molto. Questa sembra essere la soluzione più semplice che abbia mai visto. Molti altri non sono compatibili con API inferiori ecc.


1

Ho avuto lo stesso problema. ViewPageresegue altri eventi del ciclo di vita del frammento e non sono riuscito a cambiare quel comportamento. Ho scritto un semplice cercapersone usando frammenti e animazioni disponibili. SimplePager


1

L'ho usato e ha funzionato!

mContext.getWindow().getDecorView().isShown() //boolean

1

Supporto SectionsPagerAdapter con frammenti secondari, quindi dopo un sacco di mal di testa ho finalmente ottenuto la versione funzionante basata sulle soluzioni di questo argomento:

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;
    }
}

Hai dimenticato di menzionare, una volta aggiunto il frammento figlio al set di frammenti padre fragment.setUserVisibleHint (true);
Andoctorey,

E non dimenticare di usare getChildFragmentManager () invece di getFragmentManager () per aggiungere frammenti figlio.
Andoctorey,

0

Si noti che setUserVisibleHint(false)non viene chiamato all'arresto di attività / frammento. Dovrai comunque controllare start / stop per correttamente register/unregistertutti gli ascoltatori / ecc.

Inoltre, otterrai setUserVisibleHint(false)se il tuo frammento inizia in uno stato non visibile; non vuoi unregisterlì perché non ti sei mai registrato prima in quel 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
    }
}

0

Un modo semplice di implementazione che sta controllando se l'utente ha effettuato l'accesso prima di andare al frammento.

Nella tua MainActivity puoi fare qualcosa del genere all'interno del metodo 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;

Tuttavia, se si utilizza il cassetto di navigazione, la selezione nel cassetto sarà cambiata in Profilo anche se non siamo passati a ProfileFragment.

Per ripristinare la selezione alla selezione corrente, eseguire il codice seguente

        navigationView.getMenu().getItem(0).setChecked(true);

0

setUserVisibleHint (booleano visibile) è ora obsoleto Quindi questa è la soluzione corretta

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

In ViewPager2 e ViewPager dalla versione androidx.fragment:fragment:1.1.0puoi semplicemente usare onPause()e onResume()determinare quale frammento è attualmente visibile per l'utente. onResume()viene chiamato quando il frammento diventa visibile eonPause quando si ferma per essere visibile.

Per abilitare questo comportamento nel primo ViewPager devi passare il FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparametro come secondo argomento del FragmentPagerAdaptercostruttore.


-4

Ho ignorato il metodo Count del FragmentStatePagerAdapter associato e ho restituito il conteggio totale meno il numero di pagine da nascondere:

 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; }
     }
 }

Quindi, se ci sono 3 frammenti inizialmente aggiunti al ViewPager e solo i primi 2 dovrebbero essere mostrati fino a quando non viene soddisfatta una condizione, sovrascrivere il conteggio delle pagine impostando TrimmedPages su 1 e dovrebbe mostrare solo le prime due pagine.

Questo funziona bene per le pagine alla fine, ma non sarà di grande aiuto per quelli all'inizio o al centro (anche se ci sono molti modi per farlo).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.