[英]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上方。 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.