15

これが私の問題です。タブ付きのActionBar Sherlock、オプションメニュー付きのフラグメントを使用しているアプリがあります。エミュレーターを回転させるたびに、非表示/削除されたものも含め、すべてのフラグメントにメニューが追加されます (両方を試しました)。

設定は次のとおりです。

  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 が複数回呼び出されていることがわかります。

私は完全に困惑しています。

圧倒されることなく、できるだけ多くのコードを投稿しようとしましたが、何か不足している場合はお知らせください。

[編集] ログを追加したところ、ローテーションでフラグメントが 2 回 (またはそれ以上) 接続されていることがわかりました。私が気づいたことの 1 つは、1 回だけ呼び出される 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 クラスの異なるインスタンスに対してです。私が理解できないのは、なぜそのクラスが複数回追加されているのかということです...しかし、それは良い手がかりです。

4

5 に答える 5

7

問題が見つかったようです。多数のメニューに加えて、例外もあるため、問題と言います。

1) への呼び出し

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

これは、addTab() の呼び出しので、onTabSelected() を呼び出すという副作用があります。次に、私の TabListener は FragmentList1 を FragmentManager に追加します

2) デバイスを回転させると、アクティビティは期待どおりに破棄されますが、フラグメントは破棄されません。ローテーション後に新しいアクティビティが作成されると、次の 2 つのことが行われます。

  1. FragmentManager に追加するフラグメントの別のセットを作成します。これが多数のメニューの原因でした
  2. 次のコードを実行する onTabSelected を (setNavigationMode() 経由で) 呼び出します。

     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);
     }
    

基本的に、フラグメントがすでに FragmentManager にある場合は、追加する必要はなく、表示するだけです。しかし、そこに問題があります。同じフラグメントではありません。アクティビティの以前のインスタンスによって作成されたフラグメントです。したがって、この新しく作成されたフラグメントをアタッチして表示しようとすると、例外が発生します

ソリューション。

これをすべて修正するには、いくつかの作業が必要でした。

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 にあるかどうかを確認する必要があります。存在する場合はそれらのインスタンスを使用し、そうでない場合は新しいインスタンスを作成します。これは、3 つのタブすべてに対して行われます。

m_fragment.LIST_TAG と FragmentList1.LIST_TAG_STATIC という 2 つの類似したラベルがあることに気付いたかもしれません。ああ、素敵だなぁ…(←皮肉)

TagListener をポリモーフィックに使用するために、基本クラスで次の非静的変数を宣言しました。

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

これは子孫の内部から割り当てられ、 FragmentListBase のさまざまな子孫について FragmentManager を調べることができます。

ただし、特定の子孫を作成する前に検索する必要があるため (作成する必要があるかどうかを知る必要があるため)、次の静的変数も宣言する必要があります。

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

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

誰もこのシンプルでエレガントな解決策を思いつかなかったことに失望したと言えば十分です(<-もっと皮肉)

私のためにこれを見て時間を割いてくれたジェイク・ウォートンに感謝します:)

于 2011-08-29T21:32:03.477 に答える
6
public FragmentListBase() {
    setRetainInstance(true);
    setHasOptionsMenu(true);
}

これにより、回転時に各フラグメントの個々の状態が保存/復元されます。


もう 1 つの簡単な変更transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG)として、タブで選択されたコールバックを呼び出し、選択されていないコールバックのコンテンツを削除します。

于 2011-08-29T01:32:33.350 に答える
3

ローテーション時の「積み重ね可能な」メニューで、これと非常によく似た問題がありました。タブは使用しませんが、FragmentStatePagerAdapter で ViewPager を使用するため、フラグメントを実際に再利用することはできません。2日間頭をぶつけた後、非常に簡単な解決策を見つけました。実際、問題は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;
    }
}
于 2012-11-14T21:29:07.197 に答える
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;
于 2012-06-08T17:16:50.847 に答える