【问题标题】:Double fragment rotating Android with ActionBar使用 ActionBar 旋转 Android 的双片段
【发布时间】:2012-03-16 03:39:45
【问题描述】:

我制作了一个带有 ActionBar 的简单 Android Activity,用于在 2 个片段之间切换。 在我旋转设备之前一切正常。事实上,当我旋转时,我有 2 个片段一个接一个:前一个活动片段和第一个片段。 为什么? 如果旋转破坏并重新创建我的活动,为什么我会获得 2 个片段?

示例代码:

活动

package rb.rfrag.namespace;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.os.Bundle;

    public class RFragActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Notice that setContentView() is not used, because we use the root
        // android.R.id.content as the container for each fragment

     // setup action bar for tabs
        final ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        //actionBar.setDisplayShowTitleEnabled(false);

        Tab tab;
        tab = actionBar.newTab()
                .setText(R.string.VarsTab)
                .setTabListener(new TabListener<VarValues>(
                        this, "VarValues", VarValues.class));
        actionBar.addTab(tab);

        tab = actionBar.newTab()
                .setText(R.string.SecTab)
                .setTabListener(new TabListener<SecFrag>(
                        this, "SecFrag", SecFrag.class));
        actionBar.addTab(tab);
    }
}

TabListener

package rb.rfrag.namespace;

import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;

public class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    /** Constructor used each time a new tab is created.
      * @param activity  The host Activity, used to instantiate the fragment
      * @param tag  The identifier tag for the fragment
      * @param clz  The fragment's Class, used to instantiate the fragment
      */
    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {    
        // Check if the fragment is already initialized
        if (mFragment == null) {
            // If not, instantiate and add it to the activity
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            // If it exists, simply attach it in order to show it
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(mFragment);
        }
    }
}

【问题讨论】:

    标签: android android-fragments fragment android-actionbar


    【解决方案1】:

    我已经解决了在Activity中使用onSaveInstanceStateonRestoreInstanceState来维护选中的tab,修改onTabSelected如下。

    最后的修改避免了 Android 重建它不会破坏的 Fragment。但是我不明白为什么 Activity 被旋转事件破坏,而当前的 Fragment 没有。 (你有什么想法吗?)

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
            // previous Fragment management
            Fragment prevFragment;
            FragmentManager fm = mActivity.getFragmentManager();
            prevFragment = fm.findFragmentByTag(mTag); 
            if (prevFragment != null) { 
                mFragment = prevFragment; 
            } // \previous Fragment management
    
            // Check if the fragment is already initialized
            if (mFragment == null) {
                // If not, instantiate and add it to the activity
                mFragment = Fragment.instantiate(mActivity, mClass.getName());
                ft.add(android.R.id.content, mFragment, mTag);
            } else {
                // If it exists, simply attach it in order to show it
                ft.attach(mFragment);
            }
        }
    

    【讨论】:

    • 这非常有效。关于这是否是“正确”的做事方式的任何线索?
    • 直到我发现这个,这让我发疯了......非常感谢这个答案! (仍然无法相信谷歌不会在他们的例子中解决这个问题)
    • 有趣。只有我在纵向使用android.support.v4.view.ViewPager 并在横向显示两个片段。旋转两次,我得到两个横向片段。一个叫做 android:switcher:2131099773:1 的东西——让我抓狂。我必须找到一个飞节来摆脱僵尸。
    • 经过大量的 r 和 d 这也解决了 mah 问题:)thanx @asclepix
    • 我不明白这个。旋转设备时,确实在片段上调用了 onDestroy()。在那之后它怎么还能被使用和可见?
    【解决方案2】:

    由于我使用 android.support.v4.view.ViewPager 覆盖 onTabSelected 将无济于事。但是你仍然暗示我指向了正确的方向。

    android.support.v4.app.FragmentManager 将所有片段保存在android.support.v4.app.FragmentActivityonSaveInstanceState 中。 忽略setRetainInstance — 根据您的设计,这可能会导致重复片段。

    最简单的解决方法是删除activity的orCreate中保存的fragment:

       @Override
       public void onCreate (final android.os.Bundle savedInstanceState)
       {
          if (savedInstanceState != null)
          {
             savedInstanceState.remove ("android:support:fragments");
          } // if
    
          super.onCreate (savedInstanceState);
    …
          return;
       } // onCreate
    

    【讨论】:

    • 这很好,只是当 Fragment2 进入 Landscape 时,显示 fragment1。不知道我是不是唯一一个有这个问题的人。有什么解决办法吗?
    • 也适用于您的纵向和横向解决方案。在标签之间切换后不再重叠。
    【解决方案3】:

    该解决方案实际上并不需要大量工作,以下步骤可确保在旋转屏幕时保持选项卡选择。我遇到了重叠的片段,因为在屏幕旋转时选择了我的第一个选项卡,而不是在旋转屏幕之前选择的第二个选项卡,因此第一个选项卡与第二个选项卡的内容重叠。

    这就是您的 Activity 的外观(我正在使用 ActionBarSherlock,但调整它应该很容易):

    public class TabHostActivity extends SherlockFragmentActivity {
    
       private static final String SELETED_TAB_INDEX = "tabIndex";
    
    
       @Override
       protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
    
         // Setup the action bar
         ActionBar actionBar = getSupportActionBar();
         actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    
         // Create the Tabs you need and add them to the actionBar...
    
         if (savedInstanceState != null) {
            // Select the tab that was selected before orientation change
            int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
            actionBar.setSelectedNavigationItem(index);
         }
       }
    
       @Override
       protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         // Save the index of the currently selected tab 
         outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
       }
    }
    

    这就是我的 ActionBar.TabListener 的样子(它是上述 Activity 中的私有类):

      private class MyTabsListener<T extends Fragment> implements ActionBar.TabListener {
         private Fragment fragment;
         private final SherlockFragmentActivity host;
         private final Class<Fragment> type;
         private String tag;
    
         public MyTabsListener(SherlockFragmentActivity parent, String tag, Class type) {
            this.host = parent;
            this.tag = tag;
            this.type = type;
         }
    
         @Override
         public void onTabSelected(Tab tab, FragmentTransaction transaction) {
            /*
             * The fragment which has been added to this listener may have been
             * replaced (can be the case for lists when drilling down), but if the
             * tag has been retained, we should find the actual fragment that was
             * showing in this tab before the user switched to another.
             */
            Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
    
            // Check if the fragment is already initialised
            if (currentlyShowing == null) {
              // If not, instantiate and add it to the activity
              fragment = SherlockFragment.instantiate(host, type.getName());
              transaction.add(android.R.id.content, fragment, tag);
            } else {
              // If it exists, simply attach it in order to show it
              transaction.attach(currentlyShowing);
            }
         }
    
         public void onTabUnselected(Tab tab, FragmentTransaction fragmentTransaction) {
            /*
             * The fragment which has been added to this listener may have been
             * replaced (can be the case for lists when drilling down), but if the
             * tag has been retained, we should find the actual fragment that's
             * currently active.
             */
            Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
            if (currentlyShowing != null) {
              // Detach the fragment, another tab has been selected
              fragmentTransaction.detach(currentlyShowing);
            } else if (this.fragment != null) {
              fragmentTransaction.detach(fragment);
            }
         }
    
         public void onTabReselected(Tab tab, FragmentTransaction fragmentTransaction) {
            // This tab is already selected
         }
    

    上述实现还允许根据标签替换选项卡中的片段。为此,在同一选项卡中切换片段时,我使用相同的标记名称,该名称用于添加到选项卡的初始框架。

    【讨论】:

      【解决方案4】:

      感谢 Martin 和 asclepix 提供的解决方案。我有 3 个选项卡,第一个选项卡包含 2 个片段,如下所示:

      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          tools:context=".MainActivity" >
      
          <FrameLayout 
              android:id="@+id/frActiveTask"
              android:layout_width="fill_parent"
              android:layout_height="50dp"
              android:layout_alignParentBottom="true" 
              />
      
          <FrameLayout
              android:id="@+id/frTaskList"
              android:layout_width="fill_parent"
              android:layout_height="match_parent"
              android:layout_alignParentTop="true"
              android:layout_above="@id/frActiveTask"
              />
      
      </RelativeLayout>
      

      使用onRestoreInstanceStateonSaveInstanceStatesavedInstanceState.remove("android:support:fragments"); 方法和语句几乎可以正常工作,除非您的活动选项卡不是第一个并旋转并首先单击,会出现清晰的显示,并且仅在第二次单击第一个选项卡是正确的片段显示。 调试代码后,我发现第一个addTab 总是在选项卡侦听器中调用onTabSelected 事件,并使用片段add 方法,然后当从onRestoreInstanceState 调用setSelectedNavigationItem 时,将执行detach第一个选项卡和 add 另一个选项卡。 这个不必要的add 调用已在我的解决方案中修复。

      我的活动

      protected void onCreate(Bundle savedInstanceState) {
          boolean firstTabIsNotAdded = false;
          if (savedInstanceState != null) {
              savedInstanceState.remove("android:support:fragments");
              firstTabIsNotAdded = savedInstanceState.getInt(SELETED_TAB_INDEX) != 0;
          }
          super.onCreate(savedInstanceState);
      
          setContentView(R.layout.activity_main);
      
      // codes before adding tabs
      
          actionBar = getSupportActionBar();
          actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
      
      
          tabStartAndStop = actionBar.newTab().setText(getString(R.string.tab_title_start_and_stop))
                  .setTabListener(
                          new FragmentTabListener<StartStopFragment>(this, 
                                  getString(R.string.tab_title_start_and_stop_id), 
                                  StartStopFragment.class,
                                  firstTabIsNotAdded));
          tabHistory = actionBar.newTab().setText(getString(R.string.tab_title_history))
                  .setTabListener(
                          new FragmentTabListener<HistoryFragment>(this, 
                                  getString(R.string.tab_title_history_id), 
                                  HistoryFragment.class,
                                  false));
          tabRiporting = actionBar.newTab().setText(getString(R.string.tab_title_reporting))
                  .setTabListener(
                          new FragmentTabListener<ReportingFragment>(this, 
                                  getString(R.string.tab_title_reporting_id), 
                                  ReportingFragment.class,
                                  false));
      
          actionBar.addTab(tabStartAndStop);
      
              actionBar.addTab(tabHistory);
              actionBar.addTab(tabRiporting);
      
          }
      
          @Override
          protected void onRestoreInstanceState(Bundle savedInstanceState) {
              if (savedInstanceState != null) {
                  int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
                  actionBar.setSelectedNavigationItem(index);
              }
              super.onRestoreInstanceState(savedInstanceState);
          }
      
          @Override
          protected void onSaveInstanceState(Bundle outState) {
              super.onSaveInstanceState(outState);
              // Save the index of the currently selected tab
              outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
          }
      

      以及修改后的tab监听器

      public class FragmentTabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
          private Fragment mFragment;
          private final Activity mFragmentActivity;
          private final String mTag;
          private final Class<T> mClass;
          private boolean doNotAdd;
      
          /** Constructor used each time a new tab is created.
            * @param activity  The host Activity, used to instantiate the fragment
            * @param tag  The identifier tag for the fragment
            * @param clz  The fragment's Class, used to instantiate the fragment
            */
          public FragmentTabListener(Activity activity, String tag, Class<T> clz, boolean doNotAdd) {
              mFragmentActivity = activity;
              mTag = tag;
              mClass = clz;
              this.doNotAdd = doNotAdd;
          }
      
          /* The following are each of the ActionBar.TabListener callbacks */
          public void onTabSelected(Tab tab, FragmentTransaction ft) {
      
              // Check if the fragment is already initialized
              if (mFragment == null) {
                  // If not, instantiate and add it to the activity
                  if(doNotAdd){
                      doNotAdd = false;
                  }else{
                      mFragment = Fragment.instantiate(mFragmentActivity, mClass.getName());
                      ft.add(android.R.id.content, mFragment, mTag);
                  }
              } else {
                  // If it exists, simply attach it in order to show it
                  ft.attach(mFragment);
              }
          }
      
          public void onTabUnselected(Tab tab, FragmentTransaction ft) {
              if (mFragment != null) {
                  // Detach the fragment, because another one is being attached
                  ft.detach(mFragment);
              }
          }
      
          public void onTabReselected(Tab tab, FragmentTransaction ft) {
              // User selected the already selected tab. Usually do nothing.
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2012-02-29
        • 2014-07-28
        • 1970-01-01
        • 2012-02-20
        • 2015-10-21
        • 1970-01-01
        • 2016-03-12
        • 1970-01-01
        • 2012-03-11
        相关资源
        最近更新 更多