6

次のような階層を持つアプリがあります。

FragmentTabHost (Main Activity)
  - Fragment (tab 1 content - splitter view)
    - Fragment (lhs, list)
    - Framment (rhs, content view)
  - Fragment (tab 2 content)
  - Fragment (tab 2 content)

すべてのフラグメント ビューがリソースから膨張しています。

アプリが起動すると、すべてが表示され、問題なく表示されます。最初のタブから別のタブに切り替えて再び戻ると、タブ 1 のビューを再作成しようとしてインフレ例外が発生します。

もう少し深く掘り下げると、これが起こっていることです:

  • 最初の読み込みで、スプリッター ビューを拡張すると、その 2 つの子フラグメントがフラグメント マネージャーに追加されます。
  • 最初のタブから切り替えると、ビューは破棄されますが、子フラグメントはフラグメント マネージャーに残ります。
  • 最初のタブに戻ると、ビューが再度膨張し、古い子フラグメントがまだフラグメント マネージャーにあるため、新しい子フラグメントが (膨張によって) インスタンス化されると例外がスローされます。

フラグメント マネージャーから子フラグメントを削除することでこの問題を回避し (Mono を使用しています)、例外なくタブを切り替えることができるようになりました。

public override void OnDestroyView()
{
    var ft = FragmentManager.BeginTransaction();
    ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment));
    ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment));
    ft.Commit();

    base.OnDestroyView();
}

だから私はいくつかの質問があります:

  1. 上記はこれを行う正しい方法ですか?
  2. そうでない場合、どのようにすればよいですか?
  3. いずれにせよ、タブを切り替えたときにビューステートが失われないように、インスタンスの状態を保存することはこれらすべてにどのように結びついているのでしょうか?
4

2 に答える 2

3

Mono でこれを行う方法がわかりませんが、子フラグメントを別のフラグメントに追加するにFragmentManagerは、Activity. 代わりにChildFragmentManager、ホスティングのを使用する必要がありFragmentます。

http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager() http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()

のメインFragmentManagerActivityタブを処理します。
のは分割ビューを処理します ChildFragmentManagertab1

于 2013-03-11T23:19:35.473 に答える
2

OK、私は最終的にこれを理解しました:

上で提案したように、最初にフラグメントの作成をプログラムで行うように変更し、次のように子フラグメント マネージャーに追加しました。

public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
    var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false);

    // Add fragments to the child fragment manager
    // DONT DO THIS, SEE BELOW 
    var tx = ChildFragmentManager.BeginTransaction();
    tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
    tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
    tx.Commit();

    return view;
}

予想どおり、タブを切り替えるたびに Lhs/RhsFragment の追加インスタンスが作成されますが、古い Lhs/RhsFragment の OnCreateView も呼び出されることに気付きました。そのため、タブを切り替えるたびに、OnCreateView がもう 1 回呼び出されます。タブを 10 回切り替える = OnCreateView への 11 回の呼び出し。これは明らかに間違っています。

FragmentTabHost のソース コードを見ると、タブを切り替えるときに、タブのコンテンツ フラグメントを単純に切り離して再付加していることがわかります。親 Fragment の ChildFragmentManager が子フラグメントを維持し、親フラグメントが再接続されると自動的にビューを再作成しているようです。

そのため、フラグメントの作成を OnCreate に移動しました。これは、保存された状態からロードしていない場合に限られます。

public override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

    if (savedInstanceState == null)
    {
        var tx = ChildFragmentManager.BeginTransaction();
        tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
        tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
        tx.Commit();
    }
}


public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
    // Don't instatiate child fragments here

    return inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
}

これにより、追加のビューの作成が修正され、タブの切り替えが基本的に機能するようになりました。

次の質問は、ビュー ステートの保存と復元でした。子フラグメントでは、現在選択されているアイテムを保存して復元する必要があります。もともと私はこのようなものを持っていました(これは子フラグメントのOnCreateViewです)

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
    var view = inflater.Inflate(Resource.Layout.CentresList, container, false);

    // ... other code ommitted ...

    // DONT DO THIS, SEE BELOW 
    if (savedInstance != null)
    {
        // Restore selection
        _selection = savedInstance.GetString(KEY_SELECTION);
    }
    else
    {
        // Select first item
        _selection =_items[0];  
    }

    return view;
}

これに関する問題は、タブを切り替えるときにタブ ホストが OnSaveInstanceState を呼び出さないことです。むしろ、子フラグメントは存続し、その _selection 変数はそのままにしておくことができます。

そこで、選択を管理するコードを OnCreate に移動しました。

public override void OnCreate(Bundle savedInstance)
{
    base.OnCreate(savedInstance);

    if (savedInstance != null)
    {
        // Restore Selection
        _selection = savedInstance.GetString(BK_SELECTION);
    }
    else
    {
        // Select first item
        _selection = _items[0];
    }
}

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
    // Don't restore/init _selection here

    return inflater.Inflate(Resource.Layout.CentresList, container, false);
}

タブの切り替えと向きの変更の両方で、すべてが完全に機能しているようです。

于 2013-03-14T23:02:01.287 に答える