2011-08-28 81 views
14

這是我的問題。我有一個應用程序,我正在使用帶有選項卡的選項卡和片段的ActionBar Sherlock。每次旋轉模擬器時,都會爲所有片段添加菜單,即使是那些被隱藏/刪除的片段(我都嘗試過)。onCreateOptionsMenu在ActionBar中使用標籤被調用的次數太多

這是設置:一個FragmentActivity,具有與

final ActionBar bar = getSupportActionBar(); 

    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(new FragmentList1()))); 

    bar.addTab(bar.newTab() 
     .setText("2") 
     .setTabListener(new MyTabListener(new FragmentList2()))); 

    bar.addTab(bar.newTab() 
     .setText("3") 
     .setTabListener(new MyTabListener(new FragmentList3()))); 

    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
    bar.setDisplayShowHomeEnabled(true); 
    bar.setDisplayShowTitleEnabled(true); 

的動作條中的選項卡都使用相同的監聽器:

private class MyTabListener implements ActionBar.TabListener { 
    private final FragmentListBase m_fragment; 


    public MyTabListener(FragmentListBase fragment) { 
    m_fragment = fragment; 
    } 


    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

     transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 

    transaction.commit(); 
    } 


    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

    transaction.remove(m_fragment); 
    transaction.commit(); 
    } 


    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    } 
} 

FragmentListBase的每個子類都有自己的菜單,因此所有3個亞類有:

setHasOptionsMenu(true); 

和適當的

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    Log.d(TAG, "OnCreateOptionsMenu"); 

    inflater.inflate(R.menu.il_options_menu, menu); 
} 

當我運行應用程序時,我可以看到onCreateOptionsMenu被多次調用,用於所有不同的片段。

我完全難住。

我嘗試發佈儘可能多的代碼,而不是壓倒性的,如果你發現缺少某些東西,請告知。

[編輯] 我添加了更多的日誌記錄,事實證明該片段正在附加兩次(或更多)旋轉。我注意到的一件事是,除了僅被調用一次的onCreate()方法以外,所有內容都被多次調用。

06.704:/WindowManager(72): Setting rotation to 0, animFlags=0 
06.926:/ActivityManager(72): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=1/1/2 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=35} 
07.374:/FragmentList1(6880): onAttach 
07.524:/FragmentList1(6880): onCreateView 
07.564:/FragmentList1(6880): onAttach 
07.564:/FragmentListBase(6880): onCreate 
07.564:/FragmentList1(6880): OnCreateOptionsMenu 
07.574:/FragmentList1(6880): OnCreateOptionsMenu 
07.604:/FragmentList1(6880): onCreateView 

[編輯2]

好吧,我開始回溯到Android的代碼,並在這裏找到這部分(即我編輯,以縮短這個職位)。

/com_actionbarsherlock/src/android/support/v4/app/FragmentManager.java

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    if (mActive != null) { 
     for (int i=0; i<mAdded.size(); i++) { 
      Fragment f = mAdded.get(i); 
      if (f != null && !f.mHidden && f.mHasMenu) { 
       f.onCreateOptionsMenu(menu, inflater); 
      } 
     } 
    } 

的問題是,mAdded確實有FragmentList1的多個實例中,所以在onCreateOptionsMenu()方法是「正確「被調用3次,但對於FragmentList1類的不同實例。我不明白的是爲什麼這個班級被多次添加......但這是一個很好的領先地位。

回答

7

我似乎發現了問題。我說問題是因爲在衆多的菜單之上,現在還有一個例外。

1)調用

bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

這是到addTab呼叫()具有主叫onTabSelected的副作用()。我的TabListener然後將一個FragmentList1添加到片段管理器中。2)旋轉設備將按預期銷燬Activity,但不會破壞片段。當新的活動創建後,它會做兩件事:

  1. 創建另一組片段,它將添加到FragmentManager。這是什麼造成菜單的
  2. 呼叫onTabSelected衆人(通過setNavigationMode()),這將執行以下代碼:如果片段已經在FragmentManager

    if (null != fragmentMgr.findFragmentByTag(m_fragment.LIST_TAG)) { 
        transaction.attach(m_fragment); 
        transaction.show(m_fragment); 
    } 
    else { 
        transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 
    } 
    

基本上沒有需要添加它,只需顯示它。但問題在於此。這不是同一個片段!這是由活動的早期實例創建的片段。所以它會嘗試附加並顯示這個新創建的片段,這將導致例外

解決方案。

要解決所有這些問題,有幾件事要做。

1)我將setNavigationMode()移到了addTab()的上方。

2)這是怎麼了我建立我的標籤:

FragmentListBase fragment = (FragmentListBase)fragmentMgr.findFragmentByTag(FragmentList1.LIST_TAG_STATIC); 
    if (null == fragment) { 
    fragment = new FragmentList1(); 
    } 
    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(fragment))); 

所以在活動創造我要檢查,看看是否片段已經在FragmentManager。如果他們是我使用這些實例,如果不是那麼我創建新的。這是爲所有三個選項卡完成的。

您可能已經注意到有兩個相似的標籤:m_fragment.LIST_TAG和FragmentList1.LIST_TAG_STATIC。啊,這是可愛的...(< - 諷刺)

在ordrer用我TagListener多態我已經宣佈了下述基類的非靜態變量:

public class FragmentListBase extends Fragment { 
    public String LIST_TAG = null; 
} 

它是從內部分配後代,並允許我在FragmentManager中查找FragmentListBase的不同後代。

但是我還需要在創建它們之前搜索特定的後代(因爲我需要知道是否必須創建它們),所以我還必須聲明以下靜態變量。

public class FragmentList1 extends FragmentListBase { 
    public final static String LIST_TAG_STATIC = "TAG_LIST_1"; 

    public FragmentList1() { 
     LIST_TAG = LIST_TAG_STATIC; 
    }; 
} 

我只想說,我disapointed沒有人想出了這個簡單而優雅的解決方案(< - 更諷刺)

非常感謝給傑克沃頓誰花時間來看看這對我來說:)

+1

這個答案太複雜了。在將選項卡添加到* ActionBar *之前,只需將該調用移動到'bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS)'解決了我的問題。 – Phil

6
public FragmentListBase() { 
    setRetainInstance(true); 
    setHasOptionsMenu(true); 
} 

這將在旋轉時保存/恢復每個片段的單獨狀態。


你可能要提出的是要求在標籤中選擇回調transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG),擺脫非選擇回調內容的另一個簡單的變化。

+0

感謝您的回答傑克,但是:虛無縹緲。我將其添加到代碼中,但沒有改變。我確實增加了更多的信息,但我的問題... – MikeWallaceDev

0

只是一個相當注意你的多態標籤的挫折。

聲明你的基類,像這樣:

public abstract class ListFragmentBase { 
    protected abstract String getListTag(); 
} 

現在宣佈你的子類是這樣的:

public class FragmentList1 extends ListFragmentBase { 
    public static final String LIST_TAG = "TAG_LIST_1"; 

    @Override 
    protected String getListTag() { 
     return LIST_TAG; 
    } 
} 

現在多態的方式來獲得實例標籤是這樣的:

ListFragmentBase frag = new FragmentList1(); 
frag.getListTag(); 

靜態獲取標籤如下:

FragmentList1.LIST_TAG; 
+1

您甚至可以將靜態LIST_TAG字符串設置爲私有,以避免混淆關於使用哪個屬性。 –

+0

@DominikvonWeber您可以將LIST_TAG設置爲私有,但無法靜態訪問它。這將取決於你的需求,我猜。在解決方案中,用戶靜態地訪問它 – Dave

3

我在旋轉「堆疊」菜單時遇到了非常類似的問題。我不使用標籤,但是我使用ViewPager和FragmentStatePagerAdapter,所以我不能真正重用我的Fragments。在將我的頭撞了兩天之後,我發現了非常簡單的解決方案。事實上,問題似乎與onCreateOptionsMenu多次調用。這個小代碼片斷接受的所有問題護理(面具):

/** to prevent multiple calls to inflate menu */ 
private boolean menuIsInflated; 

@Override 
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 
    if (!menuIsInflated) { 
     inflater.inflate(R.menu.job_details_fragment_menu, menu); 
     menuIsInflated = true; 
    } 
} 
+0

糟透了,我們不得不這樣做..我發誓這是一個錯誤。 – reidisaki

1

什麼對我來說是在setHasMenuOptions(真)移動到調用活動,即在該片段被宣佈爲活動工作。我以前曾在片段的onCreate方法中使用過。

這裏是代碼片段:

@Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     FragmentManager fragmentManager = getFragmentManager(); 
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 

     ForecastFragment forecastFragment = new ForecastFragment(); 
     forecastFragment.setHasOptionsMenu(true); 
     fragmentTransaction.add(R.id.fragment, forecastFragment); 
     fragmentTransaction.commit(); 
    } 
+0

以上答案都不適用於我,但您的答案確實如此。謝謝! –

相關問題