簡體   English   中英

由於失去參考,活動會在“方向變化”中重新創建片段,如何避免這種情況?

[英]Activity recreates Fragments on Orientation Change because reference is lost, how to avoid that?

我正在嘗試學習Android Fragment並且我對Fragment管理有一個非常具體的問題,因為屏幕方向會破壞我的實現。

編輯:已經解決了我的問題,請參閱下面的“更新”。

簡潔版本:

使用靜態片段,如果更改屏幕方向,則會丟失對R.id.fragment的引用,並且Activity重新創建導致問題的Fragment因為布局中仍然存在另一個Fragment (因為可能是在XML上定義的) 。

語境:

我有一個使用默認Eclipse模板的Master / Detail工作流,並且ItemList上的每個選項卡都有一種不同類型的Fragment 理想情況下,我想做的是在片段之間切換,但是我想不使用BackStack保留它們的當前狀態,因為我想使用ItemList導航,並使用“后退”按鈕關閉應用程序。

對於該特定問題,我找不到任何解決方案,因此嘗試了許多不同的方法。 現在,我正在使用在主布局中定義的靜態片段:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >

<fragment android:name="com.example.pintproject.DevicesFragment"
    android:id="@+id/devices"
    android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="match_parent" 
        />

<fragment android:name="com.example.pintproject.ItemDetailFragment"
    android:id="@+id/detail"
    android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="match_parent" 
        />

    </FrameLayout>

在ItemListActivity onCreate() ,我在布局中查找Fragment ,然后添加它們(如果尚未創建),並且持有對當前活動Detail Fragment的引用,因此我可以將其隱藏/顯示我切換的片段至。 我使用隱藏/顯示而不是替換,因為替換會破壞Fragment:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_item_list);

    if (findViewById(R.id.item_detail_container) != null) {
        // The detail container view will be present only in the
        // large-screen layouts (res/values-large and
        // res/values-sw600dp). If this view is present, then the
        // activity should be in two-pane mode.
        mTwoPane = true;

        // In two-pane mode, list items should be given the
        // 'activated' state when touched.
        ((ItemListFragment) getSupportFragmentManager().findFragmentById(
                R.id.item_list)).setActivateOnItemClick(true);
    }




        df = (DevicesFragment) getSupportFragmentManager().findFragmentById(R.id.devices);

        if (df==null){
            df = new DevicesFragment();
            getSupportFragmentManager().beginTransaction().add(R.id.item_detail_container,df).commit();
            getSupportFragmentManager().beginTransaction().hide(df).commit();
        }

        idf = (ItemDetailFragment) getSupportFragmentManager().findFragmentById(R.id.detail);

        if (idf==null){
            idf = new ItemDetailFragment();
            getSupportFragmentManager().beginTransaction().add(R.id.item_detail_container,idf).commit();
            getSupportFragmentManager().beginTransaction().hide(idf).commit();

        }
        mContent = df;


@Override
public void onItemSelected(String id) {
    if (mTwoPane) {
        // In two-pane mode, show the detail view in this activity by
        // adding or replacing the detail fragment using a
        // fragment transaction.


        switch (Integer.valueOf(id)){

        case 1:{

            if (idf!=null){
                getSupportFragmentManager().beginTransaction().hide(mContent).commit();
                getSupportFragmentManager().beginTransaction().show(idf).commit();
                mContent = idf;
            }

        }break;
        case 2:{

            if (df!=null){
                getSupportFragmentManager().beginTransaction().hide(mContent).commit();
                getSupportFragmentManager().beginTransaction().show(df).commit();
                mContent = df;
            }

        }break;



        }


    } else {
        // In single-pane mode, simply start the detail activity
        // for the selected item ID.
        Intent detailIntent = new Intent(this, ItemDetailActivity.class);
        detailIntent.putExtra(ItemDetailFragment.ARG_ITEM_ID, id);
        startActivity(detailIntent);
    }
}

問題:

使用這種方法,可以毫無問題地隱藏/顯示Fragment並保持其狀態,但是如果我進行了“方向更改”,則它們將被銷毀並重新創建。 我知道它們已被銷毀,因為我沒有使用setRetainInstance() ,但是問題是當我更改方向時, Activity丟失了對Fragment的引用,並且

df = (DevicesFragment) getSupportFragmentManager().findFragmentById(R.id.devices);

為null,因此程序將創建另一個Fragment。 如果我再次改變方向,不僅程序會重新創建兩個新的Fragments,而且還會以某種方式將兩個以上的Fragment添加到布局中,甚至沒有將它們隱藏起來,而是將它們顯示在另一個之上。

如果我使用setRetainInstance() ,則在更改Orientation時Fragment保持狀態,但是仍然對Fragment的活動引用為null,並在現有Fragment上方創建一個新的Fragment ,每個Fragment都有兩個。

例:

  • 我以橫向方向創建Fragment A和Fragment B。 兩者都可以正常工作,我可以在它們之間切換。
  • 我將方向更改為“縱向”, Fragment A和Fragment B被破壞,並創建了新的Fragment A'和Fragment B',但它們仍然可以正常工作。
  • 我再次將方向更改為``風景'', Fragment A'和Fragment B'被破壞,創建了新的Fragment A''和Fragment B'',但是屏幕上同時顯示了另一個Fragment A和Fragment B(上面一個另一個,我們稱它們為殘差),這些新的A''和B''工作正常,但顯示在殘差A和B上方。
  • 從這一點開始,每次更改方向時,都會將2個新Fragment添加到先前的Fragment中,但它們甚至不保持先前的狀態。

我希望這個例子足夠清楚。 我認為問題在於更改方向后“ Activity未保存視圖引用,再次創建了它們,但我真的不知道如何解決。

更新

我通過使用findFragmentByTag而不是findFragmentById解決了我的問題。 由於我現在可以檢索已經創建的Fragment ,因此我必須將它們添加到容器中並添加要搜索的特定標簽。

所以我的測試代碼如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_item_list);

    if (findViewById(R.id.item_detail_container) != null) {
        // The detail container view will be present only in the
        // large-screen layouts (res/values-large and
        // res/values-sw600dp). If this view is present, then the
        // activity should be in two-pane mode.
        mTwoPane = true;

        // In two-pane mode, list items should be given the
        // 'activated' state when touched.
        ((ItemListFragment) getSupportFragmentManager().findFragmentById(
                R.id.item_list)).setActivateOnItemClick(true);
    }
    df = (DevicesFragment) getSupportFragmentManager().findFragmentByTag("df");
    idf = (ItemDetailFragment) getSupportFragmentManager().findFragmentByTag("idf");



    if (savedInstanceState==null){

        if (df==null){
            df = new DevicesFragment();
            getSupportFragmentManager().beginTransaction().add(R.id.item_detail_container,df, "df").commit();
            getSupportFragmentManager().beginTransaction().hide(df).commit();
        }

        if (idf==null){
            idf = new ItemDetailFragment();
            getSupportFragmentManager().beginTransaction().add(R.id.item_detail_container,idf,"idf").commit();
            getSupportFragmentManager().beginTransaction().hide(idf).commit();

        }

    } else {


        Log.i("OUT","INSTANCE NOT NULL");
    }
    mContent = df;

}

這是完整功能,還必須為每個Fragment設置setRetainInstance(true) ,並且無論我們更改方向多少次,它們都將保持其當前狀態。

您絕不能持有對該片段的引用。 代替。 每當您需要它時,都可以在短時間內檢索參考。

public ItemListFragment getItemListFragment() {
    return ((ItemListFragment) getSupportFragmentManager().findFragmentById(
            R.id.item_list));
}

然后,每當您需要從中獲取數據時,請使用

final ItemListFragment listFragment = getItemListFragment();
if (listFragment != null) {
    // do something
}

並避免致電二傳手。 您可以定義設置器,但是更好的做法是在創建Fragment時傳遞參數,或者通過Fragment本身的getActivity()檢索數據,如下所述。

這樣做是因為Fragment生命周期並不總是與Activity 1匹配。

如果您必須從Activity調用setter,請不要忘記將值保存在Fragment的onSaveInstanceState()中(如果需要)。

所以不要打電話

setActivateOnItemClick(true);

在活動中,從片段中執行。

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    final YourActivity activity = (Activtity) getYourActivity();
    setActivateOnItemClick(activity.isMultiPane());
}

這樣,當在Activity onCreate()之后重新創建Fragment(僅在其中處理值設置的情況下)時,它將始終可以訪問該值

然后從Activity定義isMultiPane方法

public boolean isMultiPane() {
    return mTwoPane;
}

既然還沒有答案,這是我的意見:

當方向改變時,將重新創建片段,並且丟失數據,對嗎? 我認為這完全是為“ savedInstanceState”創建的:

警告 :每次用戶旋轉屏幕時,您的活動都會被破壞並重新創建。 當屏幕改變方向時,系統將銷毀並重新創建前台活動,因為屏幕配置已更改,並且您的活動可能需要加載替代資源(例如布局)。

這是一個可以解釋您如何進行娛樂的鏈接

希望這對您有用! =)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM