片段onResume从后堆栈


94

我正在使用兼容性包在Android 2.2上使用Fragments。使用片段并将片段之间的过渡添加到后台时,我想实现活动的onResume相同的行为,即,每当片段从弹出窗口中移到“前景”(用户可见)时,回栈,我想在片段中激活某种回调(例如,对共享UI资源执行某些更改)。

我看到片段框架中没有内置的回调。为了达到这个目的,有什么好的做法吗?


13
好问题。我正在尝试根据该场景的用例可见哪些片段来更改操作栏标题,这对我来说似乎是API中缺少的回调。
Manfred Moser 2012年

3
您的活动可以设置标题,前提是它知道随时显示哪些片段。另外,我想您可以做一些事情,在onCreateView弹出后将需要该片段。
PJL '01年

1
@PJL +1根据参考资料,应该这样做。但是我更喜欢听众的方式
momo

Answers:


115

由于缺少更好的解决方案,我为我准备了这个工作:假设我有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()按下“返回”后将被调用。一些注意事项:

  1. 假设您已将所有交易添加到后台(使用 FragmentTransaction.addToBackStack()
  2. 每次更改堆栈时都会激活它(您可以将其他内容存储在后堆栈中,例如动画),因此您可能会收到同一片段实例的多个调用。

3
如果对您有用,您应该接受自己的正确答案。
powerj1984

7
根据您查询的片段ID,该如何工作?每个片段有什么不同?在后堆栈中找到正确的片段?
Manfred Moser 2012年

2
@Warpzit没有调用该方法,而android docs悄悄地承认它永远不会被调用(仅当活动恢复时才调用-如果该活动已经处于活动状态,则永远不会调用)
Adam

2
荒谬的是我们必须这样做!但是很高兴有人找到了这个“功能”
stevebot 2014年

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(),无论是否已将其加载到内存中。
Brotoo13年

9
没有记录到称为getFragments()SupportFragmentManager 的方法的信息... -1:-/
Protostome,2014年

1
调用了OnResume()。但是我正在黑屏。还有其他解决此问题的方法吗?
kavie 2014年

我认为如果backStackEntryCount为0,则会导致ArrayOutOfBounds异常。我是否误解了?试图编辑帖子,但被拒绝了。
Mike T

6

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


2
这似乎不适用于应用程序兼容片段。
罗坦

那就是我用的 谢谢!
阿尔伯特·维拉·卡尔沃

最简单的解决方案!只需添加片段是否当前可见并检查,就可以设置应用栏标题。
EricH206 '01

4

Android Developers的以下部分介绍了一种通信机制,该机制创建活动的事件回调。引用其中的一行:

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

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


1
我只是在Fragment类中寻找一种方法,只要该方法显示给用户即可激活。无论是因为FragmentTransaction.add()/ replace()还是FragmentManager.popBackStack()。
oriharel 2011年

@oriharel查看最新答案。加载活动后,您会收到各种调用,分别是onAttach,onCreate,onActivityCreated,onStart,onResume。如果调用了“ setRetainInstance(true)”,则在单击该片段重新显示该片段时,将不会获得onCreate。
PJL

15
在同一活动中的片段之间单击“返回”时,永远不会调用onStart()。我尝试了文档中的大多数回调,在这种情况下都没有调用它们。请参阅我的答案以解决。
oriharel 2011年

兼容compat lib v4的hmm我看到了片段的onResume。我确实喜欢@oriharel的答案,因为它确实区分了通过popBackStack可见而不是仅被带到前台。
PJL '01年

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不是单例,它仅管理Activity中的片段,因此在每个Activity中它都是新的。另外,到目前为止,该解决方案并未考虑使用ViewPager来调用setUserVisibleHint()方法来帮助控制Fragment的可见性。

处理此问题时,可以随意使用以下类(使用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

我用枚举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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.