問題:ViewPager
の中のフラグメントonResume()
は、フラグメントが実際に見えるようになる前に起動されます。
たとえば、ViewPager
とFragmentPagerAdapter
を持つ2つのフラグメントがあります。 2番目のフラグメントは許可されたユーザーにのみ利用可能であり、フラグメントが表示されたら(アラートダイアログを使用して)ログインをユーザーに要求する必要があります。
ただし、ViewPager
は、2番目のフラグメントをキャッシュするために最初のフラグメントが表示されたときに2番目のフラグメントを作成し、ユーザーがスワイプを開始したときにそれを表示させるようにします。
そのため、onResume()
イベントは、表示されるずっと前に2番目のフラグメントで発生します。そのため、適切なタイミングで2番目のフラグメントが表示されてダイアログが表示されたときに発生するイベントを見つけようとしています。
どうすればこれができますか?
_ 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-r11
でFragmentStatePagerAdapter
を使用している場合、プリキャッシュされたフラグメントも「表示」状態(実際には表示されない)になります。
上書きします。
public class MyFragment extends Fragment {
@Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
// ...
}
}
// ...
}
フラグメントのフォーカス状態をキャプチャするには、ViewPagerの1つのフラグメントだけが実際にそのメニュー項目を親アクティビティの項目と一緒に配置できるため、これが「表示」状態の最適な状態であると思います。
ViewPagerでフラグメントがいつ表示されるかを判断する方法
あなたはあなたのsetUserVisibleHint
の中でFragment
をオーバーライドすることによって以下をすることができます:
public class MyFragment extends Fragment {
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
}
else {
}
}
}
これはあなたが期待する通常の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
}
これは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
}
});
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;
}
}
説明:
fragmentResume
、fragmentVisible
:myUIUpdate()
のonCreateView()
が、再開時にではなく、フラグメントが作成され表示されている場合にのみ呼び出されるようにします。また、1番目のタブにいるときの問題も解決し、2番目のタブが表示されていなくても作成されます。これはそれを解決し、onCreate
のときにフラグメント画面が表示されるかどうかを確認します。
fragmentOnCreated
:フラグメントが表示されないようにし、フラグメントを初めて作成するときに呼び出されないようにします。そのため、このif節は、フラグメントからスワイプしたときにのみ呼び出されます。
UpdateこのコードはすべてBaseFragment
code like this に入れてメソッドをオーバーライドできます。
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);
}
}
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
それが助けを願っています
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();
}
}
そのためには Fragment.onHiddenChanged()
をオーバーライドしてください。
public void onHiddenChanged(boolean hidden)
フラグメントの(
isHidden()
によって返される)非表示状態が変更されたときに呼び出されます。断片は隠れずに始まります。フラグメントがそれから状態を変えるときはいつでも、これは呼ばれるでしょう。パラメーター
hidden
-boolean
:フラグメントが非表示になった場合はtrue、表示されていない場合はfalseです。
私はonCreateOptionsMenu
とonPrepareOptionsMenu
メソッドがフラグメント 実際 visibleの場合にのみ呼び出されることを考え出しました。私はOnPageChangeListener
を試してみましたが、そのような振る舞いをするメソッドを見つけることができませんでしたが、状況にはうまくいきませんでした、例えばonCreate
メソッドで初期化された変数が必要です。
そのため、これら2つの方法を回避策としてこの問題に使用することができます。
私は、これがより良い解決策だが最善ではないと思う。私はこれを使用しますが、同時により良い解決策を待ちます。
よろしく。
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.
}
}
もう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();
}
}
}
次のコードをフラグメントの内側に追加
@Override
public void setMenuVisibility(final boolean visible)
{
super.setMenuVisibility(visible);
if (visible && isResumed())
{
}
}
これを試してください、それは私のために働きます:
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
}else
{}
}
MVPの特別なケースでは、フラグメントがプレゼンターにビューが見えるようになったことを通知する必要があり、プレゼンターはDaggerによってfragment.onAttach()
にインジェクトされます。
setUserVisibleHint()
では十分ではありません。対処する必要がある3つの異なるケースを検出しました(onAttach()
はプレゼンターがいつ利用可能になるかを知るために言及されています)。
フラグメントが作成されました。システムは以下の呼び出しを行います。
setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
onAttach()
...
onResume()
フラグメントはすでに作成されており、ホームボタンが押されています。アプリをフォアグラウンドに復元するとき、これは次のように呼ばれます。
onResume()
向きの変更:
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);
}
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などと互換性がありません。
私は同じ問題を抱えていました。 ViewPager
は他のフラグメントライフサイクルイベントを実行し、その振る舞いを変えることはできませんでした。フラグメントと利用可能なアニメーションを使って簡単なページャーを書きました。 SimplePager
私は子フラグメントで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;
}
}
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
}
}
これを実装する簡単な方法は、ユーザーが の前 にログインしているかどうかを確認することです。
あなたの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);
私はこれを使いました、そしてそれはうまくいきました!
mContext.getWindow().getDecorView().isShown() //boolean