it-swarm.com.de

So bestimmen Sie, wann das Fragment in ViewPager sichtbar wird

Problem: Das Fragment onResume() in ViewPager wird ausgelöst, bevor das Fragment tatsächlich sichtbar wird.

Zum Beispiel habe ich 2 Fragmente mit ViewPager und FragmentPagerAdapter. Das zweite Fragment ist nur für berechtigte Benutzer verfügbar, und ich muss den Benutzer auffordern, sich anzumelden, wenn das Fragment sichtbar wird (mithilfe eines Alarmdialogfelds).

ABER die ViewPager erstellt das zweite Fragment, wenn das erste sichtbar ist, um das zweite Fragment zwischenzuspeichern, und macht es sichtbar, wenn der Benutzer mit dem Wischen beginnt.

Das Ereignis onResume() wird also im zweiten Fragment ausgelöst, lange bevor es sichtbar wird. Deshalb versuche ich, ein Ereignis zu finden, das ausgelöst wird, wenn das zweite Fragment sichtbar wird, um im richtigen Moment einen Dialog anzuzeigen.

Wie kann das gemacht werden?

671
4ntoine

UPDATE: Android Support Library (Rev 11) schließlich Problem mit dem sichtbaren Hinweis für Benutzer behoben Wenn Sie nun die Support-Bibliothek für Fragmente verwenden, können Sie getUserVisibleHint() verwenden oder setUserVisibleHint() außer Kraft setzen, um die Änderungen als zu erfassen von gorns Antwort beschrieben.

UPDATE 1 Hier ist ein kleines Problem mit getUserVisibleHint(). Dieser Wert ist standardmäßig true.

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

Es kann also ein Problem auftreten, wenn Sie versuchen, es zu verwenden, bevor setUserVisibleHint() aufgerufen wurde. Als Problemumgehung können Sie den Wert in der onCreate-Methode wie folgt festlegen.

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

Die veraltete Antwort:

In den meisten Anwendungsfällen zeigt ViewPager nur jeweils eine Seite, aber die im Cache zwischengespeicherten Fragmente werden auch in den Status "sichtbar" (tatsächlich unsichtbar) versetzt, wenn Sie FragmentStatePagerAdapter in Android Support Library pre-r11 verwenden.

Ich überschreibe:

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

Um den Fokusstatus von fragment zu erfassen, den ich für den am besten geeigneten Status der "Sichtbarkeit" halte, meinen Sie, da nur ein Fragment in ViewPager seine Menüelemente zusammen mit Elementen der übergeordneten Aktivität platzieren kann.

493
Oasis Feng

So bestimmen Sie, wann das Fragment in ViewPager sichtbar wird

Sie können Folgendes tun, indem Sie setUserVisibleHint in Ihrer Fragment überschreiben:

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

Dies scheint das normale Verhalten von onResume() wiederherzustellen, das Sie erwarten würden. Es funktioniert gut, wenn Sie die Home-Taste drücken, um die App zu verlassen, und dann die App erneut aufrufen. onResume() wird nicht zweimal hintereinander aufgerufen.

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

Hier ist eine andere Möglichkeit, onPageChangeListener zu verwenden:

  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

  }
});
59
Ostkontentitan

setUserVisibleHint() wird manchmal voronCreateView() aufgerufen und manchmal danach Probleme verursacht. 

Um dies zu überwinden, müssen Sie isResumed() auch in der setUserVisibleHint()-Methode überprüfen. In diesem Fall wurde mir jedoch klar, dass setUserVisibleHint()only genannt wird, wenn Fragment wieder aufgenommen und sichtbar ist, NICHT wenn erstellt.

Wenn Sie also etwas aktualisieren möchten, wenn Fragment visible ist, setzen Sie Ihre Aktualisierungsfunktion in onCreate() und setUserVisibleHint():

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

UPDATE: Noch immer wurde mir bewusst, dass myUIUpdate() manchmal zweimal aufgerufen wird. Wenn Sie 3 Registerkarten haben und dieser Code auf der zweiten Registerkarte steht, wird beim ersten Öffnen der ersten Registerkarte auch die zweite Registerkarte erstellt, obwohl sie nicht sichtbar ist und myUIUpdate() wird aufgerufen. Wenn Sie dann auf den 2. Tab streichen, wird myUIUpdate() von if (visible && isResumed()) aufgerufen, wodurch myUIUpdate() möglicherweise zweimal in einer Sekunde aufgerufen wird. 

Das andere Problem ist !visible in setUserVisibleHint wird sowohl aufgerufen 1), wenn Sie den Fragmentbildschirm verlassen, und 2), bevor es erstellt wird, wenn Sie zum ersten Mal zum Fragmentbildschirm wechseln.

Lösung: 

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

Erklärung: 

fragmentResume, fragmentVisible: Stellt sicher, dass myUIUpdate() in onCreateView() nur aufgerufen wird, wenn ein Fragment erstellt und sichtbar ist, nicht beim Fortsetzen. Es löst auch ein Problem, wenn Sie sich auf der 1. Registerkarte befinden und die 2. Registerkarte erstellt wird, auch wenn sie nicht sichtbar ist. Dies löst das Problem und prüft, ob der Fragmentbildschirm bei onCreate sichtbar ist.

fragmentOnCreated: Stellt sicher, dass Fragment nicht sichtbar ist und nicht aufgerufen wird, wenn Sie das Fragment zum ersten Mal erstellen. Diese if-Klausel wird also nur aufgerufen, wenn Sie aus dem Fragment streichen.

Update Sie können den gesamten Code in BaseFragment code wie hier - und überschreiben.

53
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);
    }
}
24
w3hacker

Um Fragment in ViewPager sichtbar zu erkennen, bin ich ziemlich sicher, dass nur die Verwendung vonsetUserVisibleHint nicht ausreicht.
Hier ist meine Lösung, um zu überprüfen, ob ein Fragment sichtbar oder unsichtbar ist. Wechseln Sie beim Starten von Viewpager zuerst zwischen den Seiten und wechseln Sie zu einer anderen Aktivität/Fragment/Hintergrund/Vordergrund`

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

ERKL&AUML;RUNG Sie können den Logcat-Code unten sorgfältig überprüfen, dann wissen Sie vielleicht, warum diese Lösung funktioniert

Erster Start

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

Gehen Sie zu Seite 2

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

Gehe zu Seite3

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

Zum Hintergrund gehen:

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

Zum Vordergrund gehen

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

DEMO Projekt hier

Ich hoffe es hilft

19
Linh

Überschreiben Sie setPrimaryItem() in der Unterklasse FragmentPagerAdapter. Ich benutze diese Methode und sie funktioniert gut.

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

Override Fragment.onHiddenChanged() dafür.

public void onHiddenChanged(boolean hidden)

Wird aufgerufen, wenn sich der verborgene Status (wie von isHidden() zurückgegeben) des Fragments geändert hat. Fragmente beginnen nicht verborgen; Dies wird immer dann aufgerufen, wenn das Fragment seinen Zustand ändert.

Parameter
hidden - boolean: True, wenn das Fragment jetzt ausgeblendet ist, false, wenn es nicht sichtbar ist.

10
dan

Ich habe herausgefunden, dass onCreateOptionsMenu- und onPrepareOptionsMenu-Methoden nur im Fall des Fragments really visible aufgerufen werden. Ich konnte keine Methode finden, die sich wie diese verhält, auch habe ich OnPageChangeListener versucht, aber es hat für die Situationen nicht funktioniert, zum Beispiel brauche ich eine in onCreate Methode initialisierte Variable.

Daher können diese beiden Methoden als Problemumgehung für dieses Problem verwendet werden, insbesondere für kleine und kurze Aufgaben.

Ich denke, das ist die bessere Lösung, aber nicht die beste. Ich werde dies verwenden, aber gleichzeitig auf eine bessere Lösung warten.

Grüße.

4
ismailarilik

Bei der Arbeit mit FragmentStatePagerAdapters und 3 Registerkarten ist das gleiche Problem aufgetreten. Ich musste einen Dilaog anzeigen, wenn auf die erste Registerkarte geklickt wurde, und beim Klicken auf andere Registerkarten ausgeblendet werden.

Das Überschreiben von setUserVisibleHint() allein half nicht, das aktuell sichtbare Fragment zu finden.

Beim Anklicken aus dem 3. Tab -----> 1. Tab ..__ wurde es zweimal für das 2. Fragment und für das 1. Fragment ..__ ausgelöst. Ich kombinierte es mit der Methode 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
Aman Arora

Eine andere Lösung hier gepostet, die das SetPrimaryItem im Pageradapter von kris larson überschreibt hat bei mir fast funktioniert. Diese Methode wird jedoch für jedes Setup mehrmals aufgerufen. Ich habe auch NPE aus Ansichten usw. im Fragment erhalten, da dies nicht die ersten Male ist, in denen diese Methode aufgerufen wird. Mit den folgenden Änderungen funktionierte das für mich:

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
Gober

Fügen Sie folgenden Code in Fragment hinzu

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

     }
}
2
Afzaal Iftikhar

Probieren Sie es aus, es ist Arbeit für mich:

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

        if (hidden) {

        }else
        {}
    }
2
Kyo Huu

Wir haben einen speziellen Fall mit MVP, bei dem das Fragment den Präsentator darüber informieren muss, dass die Ansicht sichtbar wurde, und der Präsentator von Dagger in fragment.onAttach() eingefügt wird.

setUserVisibleHint() ist nicht genug, wir haben drei verschiedene Fälle entdeckt, die angesprochen werden mussten (onAttach() wird erwähnt, damit Sie wissen, wann der Moderator verfügbar ist):

  1. Fragment wurde gerade erstellt. Das System führt folgende Aufrufe durch:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
    
  2. Fragment bereits erstellt und Home-Taste gedrückt. Beim Wiederherstellen der App im Vordergrund wird Folgendes aufgerufen:

    onResume()
    
  3. Orientierungsänderung:

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

Wir möchten, dass der Hinweis zur Sichtbarkeit nur einmal beim Präsentator ankommt.

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

Erkennen mit focused view!

Das funktioniert für mich

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

Beachten Sie, dass setUserVisibleHint(false) beim Stop der Aktivität/des Fragments nicht aufgerufen wird. Sie müssen immer noch Start/Stop prüfen, um register/unregister Listener/etc.

Außerdem erhalten Sie setUserVisibleHint(false), wenn Ihr Fragment in einem nicht sichtbaren Zustand beginnt. Sie möchten dort nicht unregister, da Sie sich in diesem Fall noch nie registriert haben.

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

Dieses Problem ist aufgetreten, als ich versuchte, einen Timer auszulösen, wenn das Fragment im Viewpager auf dem Bildschirm für den Benutzer sichtbar war. 

Der Timer wurde immer gestartet, unmittelbar bevor das Fragment vom Benutzer gesehen wurde .. Dies ist darauf zurückzuführen, dass die onResume()-Methode im Fragment aufgerufen wird, bevor das Fragment sichtbar wird.

Meine Lösung bestand darin, die onResume()-Methode zu überprüfen. Ich wollte eine bestimmte Methode "foo ()" nennen, wenn Fragment 8 das aktuelle Fragment des View-Pagers war. 

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

Hoffe das hilft. Ich habe gesehen, dass dieses Problem häufig auftaucht. Dies scheint die einfachste Lösung zu sein, die ich je gesehen habe. Viele andere sind nicht mit niedrigeren APIs usw. kompatibel.

1
Malone

Ich hatte das gleiche Problem. ViewPager führt andere Fragmentlebenszyklusereignisse aus, und ich konnte dieses Verhalten nicht ändern. Ich habe einen einfachen Pager geschrieben, der Fragmente und verfügbare Animationen verwendet ... /. SimplePager

1
Rukmal Dias

Ich unterstütze SectionsPagerAdapter mit untergeordneten Fragmenten, sodass ich nach vielen Kopfschmerzen endlich eine funktionierende Version habe, die auf Lösungen aus diesem Thema basiert:

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

Eine einfache Möglichkeit zur Implementierung besteht darin, zu überprüfen, ob der Benutzer in angemeldet ist, bevor zum Fragment wechselt.

In Ihrer MainActivity können Sie in der onNavigationItemSelected -Methode so etwas tun.

 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;

Wenn Sie jedoch die Navigationsleiste verwenden, hat sich die Auswahl in der Schublade zu Profil geändert, obwohl wir nicht zu ProfileFragment gegangen sind.

Um die Auswahl auf die aktuelle Auswahl zurückzusetzen, führen Sie den folgenden Code aus

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

Ich habe das benutzt und es hat funktioniert!

mContext.getWindow().getDecorView().isShown() //boolean
0
Ali Akram