Jak ustalić, kiedy fragment stanie się widoczny w ViewPager

754

Problem: Fragment onResume()w ViewPagerwypala przed fragment staje się rzeczywiście widoczne.

Na przykład mam 2 fragmenty z ViewPageri FragmentPagerAdapter. Drugi fragment jest dostępny tylko dla autoryzowanych użytkowników i muszę poprosić użytkownika o zalogowanie się, gdy fragment stanie się widoczny (za pomocą okna dialogowego alertu).

ALE ViewPagertworzy drugi fragment, gdy pierwszy jest widoczny, aby buforować drugi fragment i sprawia, że ​​jest widoczny, gdy użytkownik zaczyna przesuwać.

Tak więc onResume()zdarzenie jest uruchamiane w drugim fragmencie na długo zanim stanie się widoczne. Właśnie dlatego próbuję znaleźć zdarzenie, które zostanie uruchomione, gdy drugi fragment stanie się widoczny, aby w odpowiednim momencie pokazać okno dialogowe.

Jak można to zrobić?

4ntoine
źródło
18
„Mam 2 fragmenty z ViewPager i FragmentPagerAdapter. Drugi fragment może być dostępny tylko dla autoryzowanych użytkowników i powinienem poprosić o zalogowanie się, gdy fragment stanie się widoczny (okno dialogowe alertu).” - IMHO, to okropne UX. Wyskakujące okno dialogowe, ponieważ użytkownik przesunął palcem w poziomie, sprawiłoby, że przyznam Ci jedną gwiazdkę w Sklepie Play.
CommonsWare
czy lepiej jest wyświetlać informacje w TextView za pomocą przycisku „Zaloguj się”? Jakie jest twoje rozwiązanie dla tej sprawy?
4ntoine
5
„Nie trzeba ładować danych, jeśli nie zostaną wyświetlone”. - nie powinieneś umieszczać go w ViewPager. W dwustronicowym pagerze obie strony zostaną załadowane natychmiast, czy ci się to podoba, czy nie. Doświadczenie użytkownika ViewPagerpowinno polegać na tym, że treść jest tam natychmiast po przesunięciu, a nie jakiś czas później. Dlatego ViewPagerinicjuje stronę przed tym, co jest widoczne, aby zapewnić wygodę użytkowania.
CommonsWare
2
Wydaje się, że ViewPager nie jest elastyczna na tyle i nie pozwala wyłączyć buforowanie od minimum setOffscreenPageLimit wynosi 1: stackoverflow.com/questions/10073214/... . Nie widzę żadnego powodu tego i oczekiwanym zachowaniem (w przypadku obowiązkowego buforowania) jest utworzenie fragmentu, ALE odpalenie fragmentu ogniwa onResume (), gdy fragment stanie się widoczny.
4ntoine
1
Nieco później, ale dla każdego, kto ma ten sam problem, możesz wypróbować bibliotekę FragmentViewPager (jestem autorem), która zajmuje się tym problemem i oferuje kilka dodatkowych funkcji. Na przykład sprawdź stronę GitHub projektu lub tę odpowiedź dotyczącą przepełnienia stosu .
S. Brukhanda,

Odpowiedzi:

582

Jak ustalić, kiedy fragment stanie się widoczny w ViewPager

Można wykonać następujące czynności nadrzędnymi setUserVisibleHintw twojej Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
gorn
źródło
7
Dzięki dzisiejszej aktualizacji Biblioteki obsługi systemu Android (wersja 11) problem widocznej podpowiedzi został w końcu naprawiony. Teraz można bezpiecznie korzystać z widocznej wskazówki dla ViewPager.
Oasis Feng
58
Odkryłem, że metoda setUserVisibleHint zostaje wywołana PRZED wywołaniem metody onCreateView, co utrudnia śledzenie inicjalizacji.
AndroidDev,
11
@AndroidDev Jeśli chcesz uruchomić jakiś kod, gdy podpowiedź jest prawdziwa, ale wymaga już zainicjowanego drzewa widoku, po prostu owiń ten blok kodu, isResumed()aby uniknąć NPE. Działa mi dobrze.
Ravi Thapliyal
13
To po prostu śmieszne, że istnieje tak wiele różnych hacków dla czegoś, co SDK powinien domyślnie zapewniać.
Mike6679,
15
setUserVisibleHintjest teraz przestarzałe
SR
525

AKTUALIZACJA : Android Support Library (rev 11) w końcu naprawił problem z widoczną wskazówką dla użytkownika , teraz jeśli używasz biblioteki wsparcia dla fragmentów, możesz bezpiecznie użyć getUserVisibleHint()lub zastąpić, setUserVisibleHint()aby przechwycić zmiany zgodnie z opisem w odpowiedzi Gorn.

AKTUALIZACJA 1 Oto jeden mały problem z getUserVisibleHint(). Ta wartość jest domyślnie true.

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

Może więc występować problem, gdy próbujesz go użyć przed setUserVisibleHint()wywołaniem. Aby obejść onCreateten problem, możesz ustawić wartość w metodzie takiej jak ta.

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

Nieaktualna odpowiedź:

W większości przypadków użycia, ViewPagertylko pokazać jedną stronę na raz, ale wstępnie buforowane fragmenty są również umieścić na „widoczny” (w rzeczywistości niewidzialnej stanu), jeśli używasz FragmentStatePagerAdapterw Android Support Library pre-r11.

Zastępuję:

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

Aby uchwycić stan skupienia fragmentu, który moim zdaniem jest najbardziej odpowiednim stanem „widoczności”, masz na myśli, ponieważ tylko jeden fragment w ViewPager może faktycznie umieścić elementy menu razem z elementami działania rodzica.

Oasis Feng
źródło
73
Zachowaj ostrożność, aby użyć getActivity (), przy pierwszym uruchomieniu będzie ona pusta.
vovkab
10
Zauważ, że setUserVisibleHint () zawsze działał poprawnie w FragmentPagerAdapter.
louielouie
8
To rozwiązanie (a także gorn) jest nieco opóźnione. W przypadkach, gdy przeglądarka jest przesuwana szybko i wielokrotnie, metoda ta zostanie wywołana z opóźnieniem (gdy fragment nie jest już widoczny / niewidoczny).
AsafK
3
Dostaję truefunkcję getUserVisibleHint (), kiedy onCreateOptionsMenujest wywoływana, a kiedy setUserVisibleHintjest wywoływana, wydaje się, że menu nie zostało jeszcze utworzone. Ostatecznie dostaję dwie opcje Menu dodane, gdy chcę tylko menu z widocznego fragmentu. Wszelkie sugestie w tym zakresie?
cYrixmorten
13
setUserVisibleHintjest już przestarzałe
SR o
143

Wydaje się, że przywraca to normalne onResume()zachowanie, którego można się spodziewać. Dobrze się gra, naciskając klawisz Home, aby wyjść z aplikacji, a następnie ponownie wchodząc do aplikacji. onResume()nie jest wywoływany dwa razy z rzędu.

@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
źródło
4
Dokładnie tego szukałem. Rozwiązuje problem z setUserVisibleHintwywoływaniem wcześniej onCreateViewi setUserVisibleHintnie jest wywoływane, jeśli aplikacja przechodzi w tło, a następnie na pierwszym planie. Niesamowite! Dziękuję Ci!
Alex
11
Myślę, że samodzielne wywołanieResume jest dość złym pomysłem, ale poza tym wszystko, czego potrzebujesz, aby odpowiedzieć na pytanie tego postu, znajduje się w funkcji setUserVisibleHint!
Quentin G.
4
Wolę wdrożyć prostą onVisibleToUser()metodę i wywołać ją z onResume()i od setUserVisibleHint(boolean)zamiast wywoływać onResume()siebie i zakłócać wywołania zwrotne w cyklu życia. W przeciwnym razie myślę, że to podejście działa dobrze, dzięki!
Stephan Henningsen
1
Tak, zgadzam się z powyższymi komentarzami. Ogólnie staraj się unikać wywoływania metod publicznych z metod publicznych w swojej klasie. Utwórz metodę prywatną i wywołaj ją z obu metod publicznych.
Johan Franzén
4
setUserVisibleHint jest przestarzałe!
Hamid Reza
70

Oto inny sposób użycia 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
źródło
1
To rozwiązanie działa dobrze, dzięki. To powinno być zalecane rozwiązanie dla osób budujących starsze platformy korzystające z pakietów obsługi fragmentów (v4)
Frank Yin
7
Warto zauważyć, że nie może to dać oczekiwanego rezultatu, jeśli używasz FragmentStatePagerAdapter i tworzysz nową instancję Fragment w getItem (), ponieważ ustawiałbyś stan tylko na nowym fragmencie, a nie na tym, który uzyskał podgląd
Ben Pearson,
1
To rozwiązanie jest dobre, jeśli chcesz wykonać coś, gdy fragment stanie się widoczny. Nie utrzymuje widocznego stanu fragmentu (co oznacza, że ​​nie wie, kiedy fragment jest niewidoczny).
AsafK
Jestem tego samego rodzaju problemem. pomóż mi rozwiązać problem. Mój post: stackoverflow.com/questions/23115283/…
Jeeten Parmar
daje to wyjątek wskaźnika zerowego dla adaptera, którego używam w metodzie iamVisible fragmentu. Próbuję ustawić dane otrzymane z sieci tylko wtedy, gdy fragment jest widoczny.
zaphod100.10
58

setUserVisibleHint()jest wywoływany czasami przed, onCreateView() a czasem po którym powoduje problemy.

Aby temu zaradzić trzeba sprawdzić isResumed(), jak również wewnątrz setUserVisibleHint()metody. Ale w tym przypadku zdałem sobie sprawę, że setUserVisibleHint()zostanie wywołane tylko wtedy, gdy Fragment zostanie wznowiony i będzie widoczny, NIE po utworzeniu.

Więc jeśli chcesz zaktualizować gdy fragment jest czymś visible, umieścić zarówno w funkcji aktualizacji onCreate()i setUserVisibleHint():

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

AKTUALIZACJA: Nadal zdaję sobie sprawę myUIUpdate(), że czasami wywoływane są dwa razy, ponieważ jeśli masz 3 karty, a ten kod znajduje się na drugiej karcie, po pierwszym otwarciu pierwszej karty tworzona jest również druga karta, nawet jeśli nie jest widoczna i myUIUpdate()jest wywoływana. Następnie po przesunięciu do drugiej zakładki wywoływana jest opcja myUIUpdate()from , if (visible && isResumed())w wyniku czego myUIUpdate()może zostać wywołana dwukrotnie w ciągu sekundy.

Drugim problemem jest !visiblew setUserVisibleHintmomęcie zarówno 1), gdy wyjdziesz z fragmentu ekranu i 2), zanim zostanie utworzony, po przełączeniu na ekranie fragment raz pierwszy.

Rozwiązanie:

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

Wyjaśnienie:

fragmentResume, fragmentVisible: Sprawia, że myUIUpdate()in onCreateView()jest wywoływane tylko wtedy, gdy fragment jest tworzony i widoczny, a nie przy wznowieniu. Rozwiązuje również problem, gdy jesteś na 1. karcie, 2. karta jest tworzona, nawet jeśli nie jest widoczna. To rozwiązuje i sprawdza, czy fragment ekranu jest widoczny, kiedy onCreate.

fragmentOnCreated: Sprawia, że ​​fragment nie jest widoczny i nie jest wywoływany przy pierwszym utworzeniu fragmentu. Więc teraz, jeśli klauzula zostanie wywołana tylko po przesunięciu fragmentu.

Aktualizacja Możesz umieścić cały ten kod w BaseFragmentkodzie takim jak ten i zastąpić metodę.

Jemshit Iskenderov
źródło
1
Twoje rozwiązanie działa idealnie, z wyjątkiem jednego scenariusza, gdy fragment jest otwierany normalnie, a następnie klikasz jakiś przycisk, aby zastąpić go innym fragmentem, a następnie klikasz z powrotem, aby wyskoczyć nowy fragment z backstacka, w takim przypadku sprawa setUserVisibleHintnie została wywołana. ! a wewnątrz onCreateViewmetody fragmentVisiblebyło false! więc fragment okazał się pusty ...! jakieś pomysły.?
Alaa AbuZarifa,
28

Aby wykryć Fragmentw trybie ViewPagerwidzialnym, jestem całkiem pewien, że samo użycie setUserVisibleHint nie wystarczy.
Oto moje rozwiązanie, aby sprawdzić, czy fragment jest widoczny czy niewidoczny. Najpierw podczas uruchamiania viewpager'a przełączaj się między stronami, przejdź do innego działania / fragmentu / tła / pierwszego planu`

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

WYJAŚNIENIE Możesz dokładnie sprawdzić logcat poniżej, więc myślę, że możesz wiedzieć, dlaczego to rozwiązanie będzie działać

Pierwsze uruchomienie

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

Idź do strony 2

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

Idź do strony 3

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

Idź do tła:

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

Idź do pierwszego planu

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

Projekt DEMO tutaj

Mam nadzieję, że to pomoże

Phan Van Linh
źródło
1
Nie działa, gdy fragment ma inny pager widoku, który również zawiera fragment.
Shihab Uddin
przepraszam, nie mogę w tej chwili opublikować odpowiedzi, ponieważ nie mam wystarczająco dużo czasu, jeśli to możliwe, proszę odesłać mój dupek o swoim problemie tutaj github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmentsłuży do wykrywania a fragment has another view pager which also consists fragment. Możesz mieszać SubChildContainerFragmenti ChildContainerFragmentdo 1 klasy. Mam nadzieję, że to pomoże. Pełną odpowiedź opublikuję później
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
źródło
2
To powinna być zaakceptowana odpowiedź. Działa również z android.app.Fragment, co jest ogromną zaletą.
RajV
setUserVisibleHintPrzestarzałe
ekashking
23

W wersji ViewPager2i ViewPagerod wersji androidx.fragment:fragment:1.1.0możesz po prostu użyć onPausei onResumewywołań zwrotnych, aby określić, który fragment jest obecnie widoczny dla użytkownika. onResumewywołanie zwrotne jest wywoływane, gdy fragment staje się widoczny i onPausekiedy przestaje być widoczny.

W przypadku ViewPager2 jest to zachowanie domyślne, ale to samo zachowanie można łatwo włączyć dla starego dobra ViewPager.

Aby włączyć to zachowanie w pierwszym programie ViewPager, musisz przekazać FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparametr jako drugi argument FragmentPagerAdapterkonstruktora.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Uwaga: setUserVisibleHint()metoda i FragmentPagerAdapterkonstruktor z jednym parametrem są teraz przestarzałe w nowej wersji Fragment z Android Jetpack.

lukjar
źródło
1
Dzięki za miłe rozwiązanie. Długo tego szukałem. Świetna robota
Anurag Srivastava
1
W przypadku ViewPager2 opcja BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT nie jest zachowaniem domyślnym, musisz ustawić ją ręcznie. Po ustawieniu tej opcji twoje rozwiązanie działało dla mnie. Dzięki.
driAn
1
Od APR 2020 jest to zaktualizowane rozwiązanie i działa jak urok.
Prakash
1
@ 4ntoine rozważ rozważenie przyjęcia tej odpowiedzi jako prawidłowej, ponieważ jest ona poprawna i większość odpowiedzi korzysta ze starej metody setUserVisibleHint
Jorn Rigter
16

Zastąp setPrimaryItem()w FragmentPagerAdapterpodklasie. Używam tej metody i działa dobrze.

@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
źródło
1
To prawie działało, ale miało problemy z NPE i było wielokrotnie nazywane. Opublikowałem poniżej alternatywę!
Gober
@Gober zapisywanie obiektu Object za pierwszym razem, a następnie sprawdzanie i ignorowanie wywołania tego samego obiektu Object, takiego jak FragmentStateAdapter zrobić w metodzie setPrimaryItem ()
wanglugao
12

Zastąp Fragment.onHiddenChanged()to.

public void onHiddenChanged(boolean hidden)

Wywoływany, gdy isHidden()zmieni się stan ukryty (zwracany przez ) fragmentu. Fragmenty zaczynają się nie ukryte; będzie to wywoływane za każdym razem, gdy fragment zmieni stan od tego.

Parametry
hidden- boolean: Prawda, jeśli fragment jest teraz ukryty, fałsz, jeśli nie jest widoczny.

dan
źródło
3
ta metoda jest już przestarzała i nie może być już używana
bluewhile
4
@bluewhile, gdzie widzisz, że jest przestarzałe? developer.android.com/reference/android/app/…
Cel
1
Po prostu wykorzystałem to z powodzeniem, a dokumentacja nie wydaje się wskazywać, że jest przestarzała.
Trevor,
1
@bluewhile, w 4.1 (api 16) nie jest jeszcze przestarzałe.
dn
8
Korzystam z interfejsu API poziomu 19 i mogę potwierdzić, że chociaż nie jest to przestarzałe, nie działa zgodnie z reklamą. onHiddenChanged nie jest wywoływany, gdy fragment jest ukryty na przykład przez inny fragment.
4

Zrozumiałem to onCreateOptionsMenui onPrepareOptionsMenumetody wywoływane tylko w przypadku fragmentu są naprawdę widoczne. Nie mogłem znaleźć żadnej metody, która zachowuje się w ten sposób, również próbowałem, OnPageChangeListenerale to nie zadziałało w sytuacjach, na przykład potrzebuję zmiennej zainicjowanej w onCreatemetodzie.

Tak więc te dwie metody można zastosować w przypadku tego problemu jako obejścia, szczególnie w przypadku małych i krótkich zadań.

Myślę, że to lepsze rozwiązanie, ale nie najlepsze. Wykorzystam to, ale jednocześnie poczekam na lepsze rozwiązanie.

Pozdrowienia.

ismailarilik
źródło
2

Inne rozwiązanie zamieszczone tutaj, nadpisujące setPrimaryItem w pageradapter autorstwa Krisa Larsona, prawie dla mnie zadziałało. Ale ta metoda jest wywoływana wiele razy dla każdej konfiguracji. Dostałem także NPE z widoków itp. W tym fragmencie, ponieważ nie jest to gotowe przy pierwszych kilku wywołaniach tej metody. Przy następujących zmianach zadziałało to dla mnie:

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
źródło
2

Dodaj następujący kod do fragmentu

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

     }
}
Afzaal Iftikhar
źródło
Działa to tylko w przypadku ViewPagerFragments. Nie dla fragmentów podczas normalnej aktywności.
AndroidGuy
2

Napotkałem ten sam problem podczas pracy z FragmentStatePagerAdapters3 kartami. Musiałem pokazywać Dilaoga za każdym razem, gdy pierwsza karta została kliknięta, i ukrywać ją, klikając inne karty.

Samo zastąpienie setUserVisibleHint()nie pomogło znaleźć bieżącego widocznego fragmentu.

Po kliknięciu z 3. zakładki -----> 1. zakładka. Uruchomił się dwukrotnie dla 2. fragmentu i 1. fragmentu. Połączyłem go z metodą 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
źródło
2

Mamy specjalny przypadek z MVP, w którym fragment musi powiadomić prezentera, że ​​widok stał się widoczny, a prezenter jest wstrzykiwany przez Sztylet fragment.onAttach().

setUserVisibleHint()to za mało, wykryliśmy 3 różne przypadki, które wymagały rozwiązania ( onAttach()zostało wspomniane, abyś wiedział, kiedy prezenter jest dostępny):

  1. Fragment właśnie został utworzony. System wykonuje następujące połączenia:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. Fragment został już utworzony i naciśnięto przycisk Home. Podczas przywracania aplikacji na pierwszym planie jest to nazywane:

    onResume()
  3. Zmiana orientacji:

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

Chcemy, aby wskazówka widoczności dotarła do prezentera tylko raz, więc tak to robimy:

@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);
}
Kołek
źródło
2

Wykrywanie przez focused view!

To działa dla mnie

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
źródło
1

Napotkałem ten problem, gdy próbowałem uruchomić licznik czasu, gdy fragment w przeglądarce był na ekranie, aby użytkownik mógł go zobaczyć.

Timer zawsze zaczynał się tuż przed wyświetleniem fragmentu przez użytkownika. Jest tak, ponieważ onResume()metoda w fragmencie jest wywoływana, zanim zobaczymy fragment.

Moim rozwiązaniem było sprawdzenie onResume()metody. Chciałem nazwać określoną metodę „foo ()”, gdy fragment 8 był bieżącym fragmentem stronicowania.

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

Mam nadzieję że to pomoże. Często widziałem ten problem. To wydaje się być najprostszym rozwiązaniem, jakie widziałem. Wiele innych nie jest kompatybilnych z niższymi interfejsami API itp.

Malone
źródło
1

Miałem ten sam problem. ViewPagerwykonuje inne fragmenty cyklu życia i nie mogłem tego zmienić. Napisałem prosty pager, używając fragmentów i dostępnych animacji. SimplePager

Rukmal Dias
źródło
1

Użyłem tego i zadziałało!

mContext.getWindow().getDecorView().isShown() //boolean
Ali Akram
źródło
1

Wspieram SectionsPagerAdapter fragmentami potomnymi, więc po dużym bólu głowy w końcu dostałem działającą wersję opartą na rozwiązaniach z tego tematu:

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
źródło
Zapomniałem wspomnieć, po dodaniu fragmentu podrzędnego do zestawu fragmentów nadrzędnych fragment.setUserVisibleHint (true);
Andoctorey,
I nie zapomnij użyć getChildFragmentManager () zamiast getFragmentManager () do dodawania fragmentów potomnych.
Andoctorey,
0

Zauważ, że setUserVisibleHint(false)nie jest wywoływane przy zatrzymaniu aktywności / fragmentu. Nadal będziesz musiał sprawdzić start / stop, aby poprawnie register/unregistersłuchać wszystkich słuchaczy / etc.

Otrzymasz również, setUserVisibleHint(false)jeśli twój fragment zaczyna się w niewidocznym stanie; nie chcesz unregistertam być, ponieważ nigdy wcześniej się nie rejestrowałeś.

@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
    }
}
jedenaście pięć
źródło
0

Prostym sposobem implementacji jest sprawdzenie, czy użytkownik jest zalogowany przed przejściem do fragmentu.

W swojej MainActivity możesz zrobić coś takiego w metodzie 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;

Jeśli jednak używasz szuflady nawigacji, wybór w szufladzie zmieni się na Profil, chociaż nie poszliśmy do ProfileFragment.

Aby zresetować wybór do bieżącego wyboru, uruchom poniższy kod

        navigationView.getMenu().getItem(0).setChecked(true);
Martin Mbae
źródło
-4

Przesłoniłem metodę Count powiązanego fragmentu FragmentStatePagerAdapter i zwróciłem całkowitą liczbę minus liczba stron do ukrycia:

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

Tak więc, jeśli początkowo dodano 3 fragmenty do ViewPager i tylko pierwsze 2 powinny być wyświetlane, dopóki nie zostanie spełniony jakiś warunek, zastąp liczbę stron, ustawiając TrimmedPages na 1 i powinny pokazywać tylko dwie pierwsze strony.

Działa to dobrze w przypadku stron na końcu, ale tak naprawdę nie pomaga w przypadku stron na początku lub w środku (chociaż istnieje wiele sposobów na zrobienie tego).

Sam jest
źródło