バックスタックからのフラグメントonResume


94

Android 2.2でフラグメントを使用するために互換性パッケージを使用しています。フラグメントを使用し、それらの間にトランジションをバックスタックに追加するとき、アクティビティのonResumeと同じ動作を達成したい、つまり、フラグメントが「フォアグラウンド」(ユーザーから見える)にされた後バックスタック、フラグメント内で(たとえば、共有されたUIリソースで特定の変更を実行するために)ある種のコールバックをアクティブ化したいのですが。

フラグメントフレームワーク内にコールバックが組み込まれていないことがわかりました。これを達成するための良い習慣はありますか?


13
すばらしい質問です。このシナリオのユースケースとして表示されるフラグメントに応じてアクションバーのタイトルを変更しようとしていますが、APIでコールバックが見つからないようです。
Manfred Moser、2012年

3
アクティビティは、どのフラグメントが一度に表示されているかがわかっていれば、タイトルを設定できます。また、私はあなたがonCreateViewポップの後にフラグメントのために呼び出される何かをすることができると思います。
PJL 2012年

1
@PJL +1参照によると、それはそれを実行する方法です。しかし、私はリスナーの方法を好む
momo 2013

回答:


115

より良い解決策がないため、これをうまく機能させました:1つのアクティビティ(MyActivity)と、互いに置き換わるフラグメントがいくつかあると仮定します(一度に1つしか表示されません)。

MyActivityで、このリスナーを追加します。

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(ご覧のとおり、私は互換性パッケージを使用しています)。

getListenerの実装:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()「戻る」が押された後に呼び出されます。ただし、いくつかの注意点:

  1. すべてのトランザクションを(を使用してFragmentTransaction.addToBackStack())バックスタックに追加したと想定しています
  2. スタックが変更されるたびにアクティブ化されます(アニメーションなどの他のものをバックスタックに格納できます)。そのため、フラグメントの同じインスタンスに対して複数の呼び出しが発生する可能性があります。

3
それがあなたのために働いたならば、あなたは正しいとあなた自身の答えを受け入れるべきです。
powerj1984

7
それはあなたがクエリしているフラグメントIDに関してどのように機能しますか?フラグメントごとにどのように異なり、正しいものはバックスタックにありますか?
Manfred Moser、2012年

2
@Warpzitそのメソッドは呼び出されず、android docsは静かにそれが呼び出されることはないことを認めます(アクティビティが再開されたときにのみ呼び出されます-そのアクティビティがすでにアクティブである場合は決して呼び出されません)
Adam

2
私たちがこれをしなければならないことはばかげています!しかし、他の誰かがこの "機能"を見つけられてうれしい
stevebot 14

3
onResume()は呼び出されません
Marty Miller

33

提案された解決策を少し変更しました。そのように私にとってはうまくいきます:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}

1
違いと、これを使用する理由を説明してください。
数学チラー

2
私のソリューションと以前のソリューションの違いは、フラグメントパイルの追加、置換、逆方向への移動など、フラグメントが変更されるたびに、最上位フラグメントの "onResume"メソッドが呼び出されることです。このメソッドにはハードコードされたフラグメントIDはありません。基本的に、メモリに既にロードされているかどうかに関係なく、変更のたびに確認しているフラグメントでonResume()を呼び出します。
Brotoo25 2013

9
getFragments()SupportFragmentManagerで呼び出されたメソッドのレコードはありません... -1:-/
Protostome

1
OnResume()が呼び出されますが、空白の画面が表示されます。この問題を解決する他の方法はありますか?
kavie 2014年

backStackEntryCountが0の場合、これによりArrayOutOfBounds例外が発生すると思います。間違っていますか?投稿を編集しようとしましたが、拒否されました。
Mike T

6

その後、popStackBack()次のコールバックを使用できます:onHiddenChanged(boolean hidden)フラグメント内


2
これは、アプリ互換フラグメントでは機能しないようです。
Lo-Tan

それが私が使ったものです。ありがとう!
Albert Vila Calvo

最も簡単な解決策!フラグメントが現在表示されていれば、チェックを追加するだけで、appbarのタイトルを設定できます。
EricH206 2017年

4

Android Developersの次のセクションでは、アクティビティへの通信メカニズムを作成する通信メカニズムについて説明します。それから行を引用するには:

これを行う良い方法は、フラグメント内にコールバックインターフェイスを定義し、ホストアクティビティがそれを実装することを要求することです。アクティビティがインターフェースを介してコールバックを受け取ると、必要に応じてレイアウト内の他のフラグメントと情報を共有できます。

編集: フラグメントはonStart(...)、フラグメントがユーザーに表示されたときに呼び出されるを持っています。同様に、onResume(...)可視でアクティブに実行されている場合。これらは、対応するアクティビティに関連付けられています。要するに:使用onResume()


1
ユーザーに表示されるたびにアクティブになるFragmentクラスのメソッドを探しています。FragmentTransaction.add()/ replace()、またはFragmentManager.popBackStack()のためかどうか。
oriharel

@oriharel更新された回答を参照してください。アクティビティが読み込まれると、onAttach、onCreate、onActivityCreated、onStart、onResumeなどのさまざまな呼び出しが行われます。「setRetainInstance(true)」を呼び出した場合、フラグメントが戻ってクリックしても再表示されても、onCreateは取得されません。
PJL 2011年

15
同じアクティビティ内のフラグメント間で「戻る」をクリックしたときに、onStart()が呼び出されることはありません。ドキュメントのコールバックのほとんどを試しましたが、そのようなシナリオではコールバックは呼び出されません。回避策については、私の回答を参照してください。
oriharel

hmmとcompat lib v4を使用しています。フラグメントのonResumeが表示されています。@oriharelの答えは好きですが、単にフォアグラウンドに移動するのではなく、popBackStackから見えるようになることを区別します。
PJL 2012年

3

私の活動でonCreate()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

このメソッドを使用して特定のFragmentをキャッチし、onResume()を呼び出します

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }

2

少し改善され、マネージャーソリューションに組み込まれました。

覚えておくべきこと。FragmentManagerはシングルトンではなく、アクティビティ内のフラグメントのみを管理するため、すべてのアクティビティで新しいものになります。また、これまでのところ、このソリューションでは、フラグメントの表示を制御するのに役立つsetUserVisibleHint()メソッドを呼び出すViewPagerは考慮されていません。

この問題に対処するときは、次のクラスを自由に使用してください(Dagger2インジェクションを使用)。活動の呼び出し:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}

1

フラグメントがバックスタックに置かれた場合、Androidは単にそのビューを破棄します。フラグメントインスタンス自体は削除されません。開始する簡単な方法は、onViewCreatedイベントをリッスンし、そこに「onResume()」ロジックを配置することです。

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }

0

これは、フラグメントがアクティビティにアタッチされている場合にonResume()を呼び出すことができる正しい答えです。または、onAttachとonDetachを使用できます。


1
例を投稿できますか?
kavie 2014年

0

フラグメントのonResume()は正常に動作します...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...

0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

最後に、RootFragmentからのFramentで効果を確認します。


0

私の回避策は、新しいタイトルに設定する前に、フラグメント内のアクションバーの現在のタイトルを取得することです。このようにして、フラグメントがポップされたら、そのタイトルに戻すことができます。

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}

0

enum FragmentTagsを使用してすべてのフラグメントクラスを定義しました。

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

FragmentTags.TAG_FOR_FRAGMENT_A.name()フラグメントタグとして渡します。

そして今

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.