来自后台堆栈的片段 onResume

Posted

技术标签:

【中文标题】来自后台堆栈的片段 onResume【英文标题】:Fragments onResume from back stack 【发布时间】:2011-09-24 02:17:52 【问题描述】:

我正在使用兼容性包在 android 2.2 中使用 Fragments。 当使用片段并将它们之间的转换添加到后台堆栈时,我想实现与活动的 onResume 相同的行为,即,每当片段在弹出后被带到“前台”(对用户可见)时backstack,我希望在片段中激活某种回调(例如,对共享 UI 资源执行某些更改)。

我看到片段框架中没有内置回调。有没有什么好的做法可以实现这一点?

【问题讨论】:

好问题。我正在尝试根据作为此场景的用例可见的片段来更改操作栏标题,这对我来说似乎是 API 中缺少的回调。 您的活动可以设置标题,因为它知道在任何时候正在显示哪些片段。另外我想你可以在onCreateView 中做一些事情,这将在弹出后被调用。 @PJL +1 根据参考资料,应该这样做。不过我更喜欢听者的方式 【参考方案1】:

由于缺乏更好的解决方案,我得到了这个为我工作: 假设我有 1 个活动 (MyActivity) 和几个相互替换的片段(一次只有一个可见)。

在 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() 将在按下“返回”后调用。不过有几点需要注意:

    假设您添加了所有 事务到后台堆栈(使用 FragmentTransaction.addToBackStack()) 将在每个堆栈上激活 更改(您可以将其他内容存储在 回栈如动画)所以 您可能会接到多个电话 片段的相同实例。

【讨论】:

如果它对你有用,你应该接受你自己的答案是正确的。 根据您查询的片段 ID,它是如何工作的?每个片段有什么不同,并且在后堆栈中找到正确的片段? @Warpzit 该方法未被调用,android 文档悄悄承认它永远不会被调用(它仅在活动恢复时调用 - 如果该活动已经处于活动状态,则永远不会调用)跨度> 荒谬的是我们必须这样做!但很高兴其他人能够找到这个“功能” onResume() 未被调用【参考方案2】:

我稍微改变了建议的解决方案。这样对我来说效果更好:

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;

【讨论】:

请解释区别,以及为什么要使用它。 我的解决方案与之前的不同之处在于,在每个片段发生变化时,例如在片段堆中添加、替换或倒退,都会调用顶部片段的“onResume”方法。此方法中没有硬编码的片段 id。基本上它在每次更改时都会在您正在查看的片段中调用 onResume(),无论它是否已经加载到内存中。 SupportFragmentManager 中没有名为getFragments() 的方法的记录... -1 :-/ OnResume() 被调用。但我得到空白屏幕..任何其他方法来解决这个问题? 如果 backStackEntryCount 为 0,我认为这会导致 ArrayOutOfBounds 异常。我弄错了吗?尝试编辑帖子但被拒绝。【参考方案3】:

popStackBack() 之后,您可以在片段中使用以下回调:onHiddenChanged(boolean hidden)

【讨论】:

这似乎不适用于应用兼容片段。 这就是我使用的。谢谢! 最简单的解决方案!只需添加一个检查片段当前是否可见,瞧,您可以设置应用栏标题。【参考方案4】:

Android Developers 的以下部分描述了一种通信机制Creating event callbacks to the activity。引用其中的一行:

这样做的一个好方法是在片段内定义一个回调接口并要求宿主活动实现它。当activity通过接口接收到回调时,可以根据需要与布局中的其他fragment共享信息。

编辑: 片段有一个onStart(...),当片段对用户可见时被调用。同样,onResume(...) 可见且正在运行时。这些与他们的活动对应物相关联。 简而言之:使用onResume()

【讨论】:

我只是在 Fragment 类中寻找一个在向用户显示时激活的方法。无论是因为 FragmentTransaction.add()/replace(),还是 FragmentManager.popBackStack()。 @oriharel 查看更新的答案。加载活动时,您将收到各种调用,onAttach、onCreate、onActivityCreated、onStart、onResume。如果您调用了“setRetainInstance(true)”,那么当片段在单击返回时重新显示时,您将不会获得 onCreate。 onStart() 在同一活动中的片段之间单击“返回”时永远不会被调用。我尝试了文档中的大多数回调,但在这种情况下都没有调用它们。请参阅我的答案以获取解决方法。 hmm with compat lib v4 我看到我的片段的 onResume。我确实喜欢@oriharel 的回答,因为它确实区分了通过 popBackStack 变得可见而不是仅仅被带到前台。【参考方案5】:

如果一个片段被放到 backstack 上,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
    

【讨论】:

【参考方案6】:

在我的活动 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;
    

【讨论】:

【参考方案7】:

稍加改进并包含在管理器解决方案中。

注意事项。 FragmentManager 不是单例,它只管理 Activity 中的 Fragment,因此在每个 Activity 中它都是新的。此外,到目前为止,此解决方案没有考虑 ViewPager 调用 setUserVisibleHint() 方法来帮助控制 Fragments 的可见性。

在处理这个问题时可以随意使用以下类(使用 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);

【讨论】:

【参考方案8】:

如果片段附加到活动,这是您可以调用 onResume() 的正确答案。或者,您可以使用 onAttach 和 onDetach

【讨论】:

你能发布示例吗?【参考方案9】:

片段的 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);
        
...

【讨论】:

【参考方案10】:
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 以查看效果

【讨论】:

【参考方案11】:

我的解决方法是先在片段中获取操作栏的当前标题,然后再将其设置为新标题。这样,一旦 Fragment 弹出,我就可以改回那个标题。

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

【讨论】:

【参考方案12】:

我使用枚举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;

【讨论】:

以上是关于来自后台堆栈的片段 onResume的主要内容,如果未能解决你的问题,请参考以下文章

将新片段添加到后台堆栈是不是会暂停当前片段?

从后台堆栈恢复片段时的 savedInstanceState

在后台堆栈中多次防止相同的片段

替换或删除后台堆栈上现有片段的代码不起作用

添加到后台堆栈时如何保持片段状态?

添加到后台堆栈时如何维护片段状态?