【问题标题】:How to resume existing Fragment from BackStack如何从 BackStack 恢复现有的 Fragment
【发布时间】:2013-08-20 18:52:07
【问题描述】:

我正在学习如何使用片段。我有三个在类顶部初始化的Fragment 实例。我将片段添加到这样的活动中:

声明和初始化:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

替换/添加:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

这些 sn-ps 工作正常。每个片段都附加到活动中,并毫无问题地保存到后台堆栈。

所以当我启动 ACB 时,堆栈如下所示:

| |
|B|
|C|
|A|
___

当我按下“返回”按钮时,B 被销毁,C 被恢复。

但是,当我第二次启动片段 A 时,它不是从后台堆栈恢复,而是添加到后台堆栈的顶部

| |
|A|
|C|
|A|
___

但我想恢复A 并销毁它上面的所有片段(如果有的话)。实际上,我只是喜欢默认的回栈行为。

我该如何做到这一点?

预期:(A 应恢复,顶部碎片应销毁)

| |
| |
| |
|A|
___

编辑:(由 A--C 建议)

这是我尝试的代码:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

问题是,当我启动A,然后启动B,然后按“返回”,B 被删除,A 被恢复。并再次按“返回”应退出应用程序。但它显示一个空白窗口,我必须第三次按下才能关闭它。

另外,当我启动 A,然后是 B,然后是 C,然后是 B 时......

预期:

| |
| |
|B|
|A|
___

实际:

| |
|B|
|B|
|A|
___

我应该用任何自定义覆盖onBackPressed(),还是我遗漏了什么?

【问题讨论】:

    标签: android android-fragments back-stack


    【解决方案1】:

    阅读documentation,有一种方法可以根据事务名称或提交提供的 id 弹出回栈。使用名称​​可能更容易,因为它不需要跟踪可能更改的数字并加强“唯一的返回堆栈条目”逻辑。

    由于每个Fragment 只需要一个返回堆栈条目,因此将返回状态名称设为片段的类名(通过getClass().getName())。然后在替换Fragment 时,使用popBackStackImmediate() 方法。如果它返回 true,则意味着在后堆栈中有一个 Fragment 的实例。如果没有,实际执行Fragment替换逻辑。

    private void replaceFragment (Fragment fragment){
      String backStateName = fragment.getClass().getName();
    
      FragmentManager manager = getSupportFragmentManager();
      boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
    
      if (!fragmentPopped){ //fragment not in back stack, create it.
        FragmentTransaction ft = manager.beginTransaction();
        ft.replace(R.id.content_frame, fragment);
        ft.addToBackStack(backStateName);
        ft.commit();
      }
    }
    

    编辑

    问题是 - 当我启动 A 然后 B,然后按返回按钮 B 被删除并恢复 A。并再次按下返回按钮应该 退出应用程序。但它显示一个空白窗口,需要再按一次 关闭它。

    这是因为FragmentTransaction 被添加到后栈中,以确保我们稍后可以将片段弹出顶部。如果返回堆栈仅包含 1 个 Fragment ,则快速解决此问题的方法是覆盖 onBackPressed() 并完成 Activity

    @Override
    public void onBackPressed(){
      if (getSupportFragmentManager().getBackStackEntryCount() == 1){
        finish();
      }
      else {
        super.onBackPressed();
      }
    }
    

    关于重复的返回堆栈条目,如果片段没有被弹出,则替换片段的条件语句与我的原始代码 sn-p 明显不同。您正在做的是添加到后台堆栈,无论后台堆栈是否被弹出。

    这样的东西应该更接近你想要的:

    private void replaceFragment (Fragment fragment){
      String backStateName =  fragment.getClass().getName();
      String fragmentTag = backStateName;
    
      FragmentManager manager = getSupportFragmentManager();
      boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
    
      if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
        FragmentTransaction ft = manager.beginTransaction();
        ft.replace(R.id.content_frame, fragment, fragmentTag);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();
      } 
    }
    

    条件发生了一些变化,因为在可见时选择相同的片段也会导致重复条目。

    实施:

    我强烈建议不要像在代码中那样将更新后的 replaceFragment() 方法分开。所有逻辑都包含在此方法中,移动部件可能会导致问题。

    这意味着您应该将更新后的replaceFragment() 方法复制到您的类中,然后进行更改

    backStateName = fragmentName.getClass().getName();
    fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
    if (!fragmentPopped) {
                ft.replace(R.id.content_frame, fragmentName);
    }
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
    

    原来如此

    replaceFragment (fragmentName);
    

    编辑#2

    要在返回堆栈更改时更新抽屉,请创建一个接受 Fragment 并比较类名的方法。如果有任何匹配,请更改标题和选择。如果存在有效的 Fragment,还可以添加 OnBackStackChangedListener 并让它调用您的更新方法。

    例如在Activity的onCreate()中,添加

    getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
    
      @Override
      public void onBackStackChanged() {
        Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
        if (f != null){
          updateTitleAndDrawer (f);
        }
    
      }
    });
    

    还有另一种方法:

    private void updateTitleAndDrawer (Fragment fragment){
      String fragClassName = fragment.getClass().getName();
    
      if (fragClassName.equals(A.class.getName())){
        setTitle ("A");
        //set selected item position, etc
      }
      else if (fragClassName.equals(B.class.getName())){
        setTitle ("B");
        //set selected item position, etc
      }
      else if (fragClassName.equals(C.class.getName())){
        setTitle ("C");
        //set selected item position, etc
      }
    }
    

    现在,每当返回堆栈发生变化时,标题和选中位置都会反映可见的Fragment

    【讨论】:

    • 感谢您的回答。 :) 它正在部分工作。我已经编辑了我的问题并取得了进展。请看)
    • @RishabhSrivastava 请记住,“pop”通常会破坏最顶层的片段。至于首先调用哪个方法,这取决于 - 看看 Fragment Lifecycle。对于您的情况,我会使用OnBackstackChangedListener,这样您就可以保证您处理的是正确的片段。
    • @RishabhSrivastava 这就是为什么我也建议使用OnBackstackChangedListener
    • @AlexanderFarber 你是对的。当我做出这个答案时,我并没有考虑instanceof :)
    • 您的解决方案听起来不错,但如果您有三个片段并单击它们A-B-C-B 并按一次返回,您将返回A 而不是返回C。那是因为popBackStackImmediate 不仅会弹出给定的标签,还会弹出上面的所有内容。那里有什么工作吗?
    【解决方案2】:

    我认为这个方法可以解决你的问题:

    public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {
    
    
        FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
        FragmentTransaction ft = manager.beginTransaction ();
    
        if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
            ft.add ( fragmentHolderLayoutId, fragment, tag );
            ft.addToBackStack ( tag );
            ft.commit ();
        }
        else {
            ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
        }
    }
    

    最初发表于 This Question

    【讨论】:

    • 请让我知道这样回答是否违反了我只是想让人们在这篇文章中看到的规则。谢谢..
    • 我不确定是否违反规则(可能不是),但这绝对有帮助
    • 第三行有什么用? -> manager.findFragmentByTag(标签);看起来它什么也没做..
    【解决方案3】:

    第 1 步:使用您的活动类实现接口

    public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            .............
            FragmentManager fragmentManager = getFragmentManager();           
            fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
        }
    
        private void switchFragment(Fragment fragment){            
          FragmentManager fragmentManager = getFragmentManager();
          fragmentManager.beginTransaction()
            .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
        }
    
        @Override
        public void onBackStackChanged() {
        FragmentManager fragmentManager = getFragmentManager();
    
        System.out.println("@Class: SummaryUser : onBackStackChanged " 
                + fragmentManager.getBackStackEntryCount());
    
        int count = fragmentManager.getBackStackEntryCount();
    
        // when a fragment come from another the status will be zero
        if(count == 0){
    
            System.out.println("again loading user data");
    
            // reload the page if user saved the profile data
    
            if(!objPublicDelegate.checkNetworkStatus()){
    
                objPublicDelegate.showAlertDialog("Warning"
                        , "Please check your internet connection");
    
            }else {
    
                objLoadingDialog.show("Refreshing data..."); 
    
                mNetworkMaster.runUserSummaryAsync();
            }
    
            // IMPORTANT: remove the current fragment from stack to avoid new instance
            fragmentManager.removeOnBackStackChangedListener(this);
    
        }// end if
       }       
    }
    

    第 2 步:当您调用另一个片段时,添加此方法:

    String backStateName = this.getClass().getName();
    
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.addOnBackStackChangedListener(this); 
    
    Fragment fragmentGraph = new GraphFragment();
    Bundle bundle = new Bundle();
    bundle.putString("graphTag",  view.getTag().toString());
    fragmentGraph.setArguments(bundle);
    
    fragmentManager.beginTransaction()
    .replace(R.id.content_frame, fragmentGraph)
    .addToBackStack(backStateName)
    .commit();
    

    【讨论】:

      【解决方案4】:

      我知道现在回答这个问题已经很晚了,但我自己解决了这个问题,并认为值得与大家分享。`

      public void replaceFragment(BaseFragment fragment) {
          FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
          final FragmentManager fManager = getSupportFragmentManager();
          BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
          transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
      
          if (fragm == null) {  //here fragment is not available in the stack
              transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
              transaction.addToBackStack(fragment.getFragmentTag());
          } else { 
              //fragment was found in the stack , now we can reuse the fragment
              // please do not add in back stack else it will add transaction in back stack
              transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
          }
          transaction.commit();
      }
      

      在 onBackPressed() 中

       @Override
      public void onBackPressed() {
          if(getSupportFragmentManager().getBackStackEntryCount()>1){
              super.onBackPressed();
          }else{
              finish();
          }
      }
      

      【讨论】:

      • @X-Black...BaseFragment 是应用程序中使用的每个片段的基类。
      【解决方案5】:
      getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
      
          @Override
          public void onBackStackChanged() {
      
              if(getFragmentManager().getBackStackEntryCount()==0) {
                  onResume();
              }    
          }
      });
      

      【讨论】:

        【解决方案6】:

        更简单的解决方案是改变这条线

        ft.replace(R.id.content_frame, A);ft.add(R.id.content_frame, A);

        请在您的 XML 布局中使用

          android:background="@color/white"
          android:clickable="true"
          android:focusable="true"
        

        Clickable表示它可以被指针设备点击或被触摸设备点击。

        Focusable 表示它可以从键盘等输入设备获得焦点。像键盘这样的输入设备无法根据输入本身决定将其输入事件发送到哪个视图,因此它们会将它们发送到具有焦点的视图。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-12
          • 1970-01-01
          • 1970-01-01
          • 2014-03-07
          • 1970-01-01
          相关资源
          最近更新 更多