web-dev-qa-db-ja.com

ViewPagerでフラグメントがいつ表示されるかを判断する方法

問題:ViewPagerの中のフラグメントonResume()は、フラグメントが実際に見えるようになる前に起動されます。

たとえば、ViewPagerFragmentPagerAdapterを持つ2つのフラグメントがあります。 2番目のフラグメントは許可されたユーザーにのみ利用可能であり、フラグメントが表示されたら(アラートダイアログを使用して)ログインをユーザーに要求する必要があります。

ただし、ViewPagerは、2番目のフラグメントをキャッシュするために最初のフラグメントが表示されたときに2番目のフラグメントを作成し、ユーザーがスワイプを開始したときにそれを表示させるようにします。

そのため、onResume()イベントは、表示されるずっと前に2番目のフラグメントで発生します。そのため、適切なタイミングで2番目のフラグメントが表示されてダイアログが表示されたときに発生するイベントを見つけようとしています。

どうすればこれができますか?

691
4ntoine

_ update _ :Android Support Library(rev 11)ついに ユーザーに見えるヒントの問題を修正しました 、フラグメント用のサポートライブラリを使用する場合、安全にgetUserVisibleHint()を使用するかsetUserVisibleHint()をオーバーライドしてキャプチャできます。 gornの答えで説明されているように変化します。

UPDATE 1 getUserVisibleHint()に関する1つの小さな問題です。この値はデフォルトでtrueです。

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

そのため、setUserVisibleHint()が呼び出される前にそれを使おうとすると問題があるかもしれません。回避策として、onCreateメソッドに次のように値を設定します。

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

時代遅れの答え:

ほとんどのユースケースでは、ViewPagerは一度に1ページしか表示しませんが、Android Support Library pre-r11FragmentStatePagerAdapterを使用している場合、プリキャッシュされたフラグメントも「表示」状態(実際には表示されない)になります。

上書きします。

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

フラグメントのフォーカス状態をキャプチャするには、ViewPagerの1つのフラグメントだけが実際にそのメニュー項目を親アクティビティの項目と一緒に配置できるため、これが「表示」状態の最適な状態であると思います。

502
Oasis Feng

ViewPagerでフラグメントがいつ表示されるかを判断する方法

あなたはあなたのsetUserVisibleHintの中でFragmentをオーバーライドすることによって以下をすることができます:

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

これはあなたが期待する通常のonResume()の振る舞いを回復するようです。ホームキーを押してアプリを終了してからアプリに再入力するとうまく動作します。 onResume()が続けて2回呼び出されることはありません。

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

これは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

  }
});
61
Ostkontentitan

setUserVisibleHint()は時々呼び出されますbeforeonCreateView()そして時には後にトラブルを引き起こします。

これを克服するには、isResumed()メソッド内でsetUserVisibleHint()もチェックする必要があります。しかし、この場合、フラグメントが再開されて表示される場合、作成されたときではなく、setUserVisibleHint()onlyと呼ばれることに気付きました。

したがって、フラグメントがvisibleのときに何かを更新する場合は、onCreate()setUserVisibleHint()の両方に更新関数を配置します。

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

UPDATE:それでもmyUIUpdate()が2回呼び出されることに気づいたのは、3つのタブがあり、このコードが2番目のタブにある場合、最初に最初のタブを開くと、2番目のタブも表示されず、myUIUpdate()が呼び出されます。次に、2番目のタブにスワイプすると、myUIUpdate()からif (visible && isResumed())が呼び出され、その結果、myUIUpdate()が1秒間に2回呼び出される場合があります。

もう1つの問題!visible in setUserVisibleHintで、1)フラグメントスクリーンから出たときと2)作成する前、切り替えたときに呼び出されます最初に画面を断片化します。

解決策:

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

説明:

fragmentResumefragmentVisiblemyUIUpdate()onCreateView()が、再開時にではなく、フラグメントが作成され表示されている場合にのみ呼び出されるようにします。また、1番目のタブにいるときの問題も解決し、2番目のタブが表示されていなくても作成されます。これはそれを解決し、onCreateのときにフラグメント画面が表示されるかどうかを確認します。

fragmentOnCreated:フラグメントが表示されないようにし、フラグメントを初めて作成するときに呼び出されないようにします。そのため、このif節は、フラグメントからスワイプしたときにのみ呼び出されます。

UpdateこのコードはすべてBaseFragment code like this に入れてメソッドをオーバーライドできます。

55
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

Fragmentの中のViewPagerを見えるようにするには、using onlysetUserVisibleHintで十分ではないと確信しています。
これがフラグメントが可視か不可視かをチェックする私の解決策です。最初にviewpagerを起動したら、ページを切り替えて別のactivity/fragment/background/foregroundに行きます。

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

DESCRIPTIONあなたは下記のlogcatを注意深くチェックすることができます、そして私はあなたがこの解決策がうまくいく理由を知っているかもしれないと思います

初回起動

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

2ページ目

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

3ページ目

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

背景に移動:

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

前景に進む

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

DEMOプロジェクトはこちら

それが助けを願っています

23
Phan Van Linh

FragmentPagerAdapterサブクラスでsetPrimaryItem()をオーバーライドします。私はこの方法を使いますが、うまくいきます。

@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

そのためには Fragment.onHiddenChanged() をオーバーライドしてください。

public void onHiddenChanged(boolean hidden)

フラグメントの( isHidden() によって返される)非表示状態が変更されたときに呼び出されます。断片は隠れずに始まります。フラグメントがそれから状態を変えるときはいつでも、これは呼ばれるでしょう。

パラメーター
hidden - boolean:フラグメントが非表示になった場合はtrue、表示されていない場合はfalseです。

10
dan

私はonCreateOptionsMenuonPrepareOptionsMenuメソッドがフラグメント 実際 visibleの場合にのみ呼び出されることを考え出しました。私はOnPageChangeListenerを試してみましたが、そのような振る舞いをするメソッドを見つけることができませんでしたが、状況にはうまくいきませんでした、例えばonCreateメソッドで初期化された変数が必要です。

そのため、これら2つの方法を回避策としてこの問題に使用することができます。

私は、これがより良い解決策だが最善ではないと思う。私はこれを使用しますが、同時により良い解決策を待ちます。

よろしく。

4
ismailarilik

FragmentStatePagerAdaptersと3つのタブを使って作業中に同じ問題が発生しました。最初のタブがクリックされるたびにDilaogを表示し、他のタブをクリックするとそれを非表示にする必要がありました。

setUserVisibleHint()だけをオーバーライドしても、現在表示されているフラグメントを見つけるのに役立ちませんでした。

3番目のタブからクリックすると-----> 1番目のタブ。 2番目のフラグメントと1番目のフラグメントに対して2回トリガーしました。私はそれを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

もう1つの解決策 ここに投稿されたkris larsonのpageradapterのsetPrimaryItemをオーバーライドしている ほぼ私のために働いた。しかし、このメソッドはセットアップごとに複数回呼び出されます。また、このメソッドが呼び出される最初の数回は準備ができていないため、フラグメントなどのビューなどから _ npe _ を取得しました。以下の変更により、これは私のために働きました:

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

次のコードをフラグメントの内側に追加

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

     }
}
2
Afzaal Iftikhar

これを試してください、それは私のために働きます:

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

        if (hidden) {

        }else
        {}
    }
2
Kyo Huu

MVPの特別なケースでは、フラグメントがプレゼンターにビューが見えるようになったことを通知する必要があり、プレゼンターはDaggerによってfragment.onAttach()にインジェクトされます。

setUserVisibleHint()では十分ではありません。対処する必要がある3つの異なるケースを検出しました(onAttach()はプレゼンターがいつ利用可能になるかを知るために言及されています)。

  1. フラグメントが作成されました。システムは以下の呼び出しを行います。

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
    
  2. フラグメントはすでに作成されており、ホームボタンが押されています。アプリをフォアグラウンドに復元するとき、これは次のように呼ばれます。

    onResume()
    
  3. 向きの変更:

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

可視性のヒントがプレゼンターに一度だけ届くようにしたいので、これを行う方法は次のとおりです。

@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

focused viewで検出中!

これは私のために働く

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

ユーザーが見ることができるようにviewpagerのフラグメントが画面上にあるときにタイマーを起動しようとしたときに、この問題が発生しました。

フラグメントはユーザーに表示される直前にタイマーが常に開始しました。これは、フラグメントが表示される前に、フラグメント内のonResume()メソッドが呼び出されるためです。

私の解決策はonResume()メソッドをチェックすることでした。フラグメント8がビューページャの現在のフラグメントであるとき、私は特定のメソッド 'foo()'を呼び出したいと思いました。

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

お役に立てれば。私はこの問題がよく現れるのを見ました。これは私が見た中で最も簡単な解決策のようです。他の多くは下位のAPIなどと互換性がありません。

1
Malone

私は同じ問題を抱えていました。 ViewPagerは他のフラグメントライフサイクルイベントを実行し、その振る舞いを変えることはできませんでした。フラグメントと利用可能なアニメーションを使って簡単なページャーを書きました。 SimplePager

1
Rukmal Dias

私は子フラグメントでSectionsPagerAdapterをサポートしているので、頭痛の種の多くの後に私はついにこのトピックからの解決策に基づいた実用的なバージョンを手に入れました:

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

setUserVisibleHint(false)はactivity/fragment stopでは呼び出されません。リスナなどを正しくregister/unregisterするには、まだ開始/停止をチェックする必要があります。

また、あなたのフラグメントが見えない状態で始まっていればsetUserVisibleHint(false)を得るでしょう。その場合は、以前に登録したことがないので、そこにunregisterを付けたくありません。

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

これを実装する簡単な方法は、ユーザーが の前 にログインしているかどうかを確認することです。

あなたのMainActivityでは、 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;

ただし、ナビゲーション用の引き出しを使用している場合は、引き出し内の選択はProfileに変更されていますが、ProfileFragmentは使用していません。

選択を現在の選択にリセットするには、以下のコードを実行してください。

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

私はこれを使いました、そしてそれはうまくいきました!

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