Android 2.2でフラグメントを使用するために互換性パッケージを使用しています。フラグメントを使用し、それらの間のトランジションをバックスタックに追加する場合、アクティビティのonResumeと同じ動作を実現したい、つまり、バックスタック、フラグメント内で何らかのコールバックをアクティブにしたい(たとえば、共有UIリソースで特定の変更を実行する)。
フラグメントフレームワーク内にはコールバックが組み込まれていないことがわかりました。これを達成するための良い習慣はありますか?
より良い解決策がないため、これは私のために働いています:私は1つのアクティビティ(MyActivity)とお互いを置き換える少数のフラグメント(一度に1つだけが表示されている)があると仮定します。
MyActivityで、このリスナーを追加します。
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
(ご覧のとおり、互換パッケージを使用しています)。
getListener implementation:
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()
が呼び出されます。ただし、いくつかの注意事項:
FragmentTransaction.addToBackStack()
を使用)提案されたソリューションを少し変更しました。そのように私にとってはうまく機能します:
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;
}
popStackBack()
の後、次のコールバックを使用できます:フラグメント内でonHiddenChanged(boolean hidden)
Android Developersの次のセクションでは、通信メカニズム アクティビティへのイベントコールバックの作成 について説明します。行を引用するには:
これを行う良い方法は、フラグメント内にコールバックインターフェイスを定義し、Hostアクティビティがそれを実装することを要求することです。アクティビティがインターフェイスを介してコールバックを受信すると、必要に応じてレイアウト内の他のフラグメントと情報を共有できます。
Edit:フラグメントには、フラグメントがユーザーに表示されるときに呼び出されるonStart(...)
があります。同様に、表示されアクティブに実行されている場合はonResume(...)
。これらは、対応するアクティビティに関連付けられています。要するに、onResume()
を使用します
私のアクティビティでは、onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
このメソッドを使用して特定のフラグメントをキャッチし、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;
}
少し改善され、マネージャーソリューションに組み込まれました。
心に留めておくべきこと。 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);
}
フラグメントがバックスタックに配置されると、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
}
私の回避策は、新しいタイトルに設定する前に、フラグメントのアクションバーの現在のタイトルを取得することです。このようにして、フラグメントがポップされたら、そのタイトルに戻すことができます。
@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();
}
フラグメントの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);
}
...
これは、フラグメントがアクティビティにアタッチされている場合にonResume()を呼び出すことができる正しい答えです。または、onAttachとonDetachを使用できます
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();
}
}
最終的にルートフラグメントからフラグメントを拡張して効果を確認します
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;
}