处理屏幕旋转上的片段重复(带有示例代码) [英] Handle Fragment duplication on Screen Rotate (with sample code)
问题描述
有一些类似的答案,但不是针对这种情况.
我的情况很简单.
我的 Activity 有两种不同的布局,一种是纵向,另一种是横向.
I have an Activity with two different layouts, one in Portrait, another in Landscape.
在纵向中,我使用
并将Fragment
动态添加到其中.
In Portrait, i use <FrameLayout>
and add Fragment
into it dynamically.
在横向中,我使用
,所以Fragment
是静态.(其实这无所谓)
In Landscape, i use <fragment>
so the Fragment
is static. (in fact this doesn't matter)
它首先从Portrait开始,然后我简单地添加了Fragment
:
It first starts in Portrait, then i added the Fragment
by simply:
ListFrag listFrag = new ListFrag();
getSupportFragmentManager().beginTransaction()
.replace(R.id.FragmentContainer, listFrag).commit();
其中 ListFrag 扩展了 ListFragment
.
然后我进行屏幕旋转.我发现 listFrag 正在横向模式下重新创建.(其中我注意到 onCreate()
方法被再次调用,并使用非空 Bundle)
Then i do a screen rotate. I found the listFrag is re-creating in the Landscape mode.
(In which i noticed the onCreate()
method is called again with a non-null Bundle)
我尝试使用 setRetainInstance(false)
就像@NPike 在 this 中所说的那样发布.但是 getRetainInstance()
默认已经是 false
.它不符合我的预期 文档说.谁能解释一下?
i tried to use setRetainInstance(false)
like @NPike said in this post. But the getRetainInstance()
is already false
by default. It does not do what i expected as the docs said. Could anyone please explain?
我正在处理的片段是一个 ListFragment
,它在 onCreate()
中执行 setListAdapter()
.所以 if (container ==null) return null;
方法 不能在这里使用.(或者我不知道如何申请).
The fragment i am dealing with, is a ListFragment
, which does setListAdapter()
in onCreate()
. So the if (container == null) return null;
method cannot be used here. (or i dont know how to apply).
我从 得到了一些提示这个帖子.我应该使用 if (bundle != null) setListAdapter(null);else setListAdapter(new ...);
在我的 ListFragment
中?但是,是否有更好的方法在片段被销毁/分离时确实删除/删除片段,而不是在其创建时进行?(如 if (container == null) return null;
方法)
I got some hints from this post. Should i use if (bundle != null) setListAdapter(null); else setListAdapter(new ...);
in my ListFragment
? But is there a nicer way to indeed removing/deleting the fragment when it is destroyed/detached, rather than doing it in its creation time? (so as the if (container == null) return null;
method)
我发现的唯一巧妙的方法是在 中执行
.但这会带来另一个问题.getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();
onSaveInstanceState()
The only neat way i found is doing getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();
in onSaveInstanceState()
. But it will raise another problems.
当屏幕被部分遮挡时,例如弹出 WhatsApp 或 TXT 对话框,片段也会消失.(这是相对较小的,只是视觉问题)
当屏幕旋转时,Activity 被完全销毁并重新创建.所以我可以在 onCreate(Bundle)
或 onRestoreInstanceState(Bundle)
中重新添加片段.但是在 (1) 的情况下,以及切换活动时,当用户返回我的活动时,不会调用 onCreate(Bundle)
和 onRestoreInstanceState(Bundle)
.我无处可重新创建 Activity(并从 Bundle 中检索数据).
When screen rotate, the Activity is completely destroyed and re-created. So i can re-add the fragment in onCreate(Bundle)
or onRestoreInstanceState(Bundle)
. But in the case of (1), as well as switching Activities, neither onCreate(Bundle)
nor onRestoreInstanceState(Bundle)
will be called when user get back to my Activity. I have nowhere to recreate the Activity (and retrieve data from Bundle).
抱歉,我没有说清楚,我已经有了一个决策,getSupportFragmentManager()...replace(...).commit();
行只在其中运行人像模式.
Sorry I didn't say it clearly that, i already have a decision making, which the getSupportFragmentManager()...replace(...).commit();
line only run in Portrait mode.
我已经提取了简单的代码,以便更好地说明情况:)
I have extracted the simple code, for better illustration of the situration :)
MainActivity.java
package com.example.fragremovetrial;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.FragmentContainer) != null) {
System.out.println("-- Portrait --"); // Portrait
ListFrag listFrag = new ListFrag();
getSupportFragmentManager().beginTransaction()
.replace(R.id.FragmentContainer, listFrag).commit();
} else {
System.out.println("-- Landscape --"); // Landscape
}
}
@Override
protected void onResume() {
super.onResume();
if (findViewById(R.id.FragmentContainer) != null) {
System.out.println("getRetainInstance = " +
getSupportFragmentManager().findFragmentById(R.id.FragmentContainer).getRetainInstance());
}
}
}
layout/activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/FragmentContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
layout-land/activity_main.xml (无所谓)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
ListFrag.java
package com.example.fragremovetrial;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.widget.ArrayAdapter;
public class ListFrag extends ListFragment {
private String[] MenuItems = { "Content A", "Contnet B" };
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("ListFrag.onCreate(): " + (savedInstanceState == null ? null : savedInstanceState));
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, MenuItems));
}
}
注意我得到了以下内容
-- 肖像--
ListFrag.onCreate(): null
getRetainInstance = 假
(旋转港口 -> 陆地)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4052dd28}]
-- 风景 --
以前聚焦的视图在保存期间报告 ID 为 16908298,但在恢复期间找不到.
(轮换土地 -> 港口)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@405166c8}]
-- 肖像 --
ListFrag.onCreate(): null
getRetainInstance = 假
(旋转港口 -> 陆地)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4050fb40}]
-- 风景 --
以前聚焦的视图在保存期间报告 ID 为 16908298,但在恢复期间找不到.
(轮换土地 -> 港口)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@40528c60}]
-- 肖像 --
ListFrag.onCreate(): null
getRetainInstance = false
-- Portrait --
ListFrag.onCreate(): null
getRetainInstance = false
(rotate Port -> Land)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4052dd28}]
-- Landscape --
Previously focused view reported id 16908298 during save, but can't be found during restore.
(rotate Land -> Port)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@405166c8}]
-- Portrait --
ListFrag.onCreate(): null
getRetainInstance = false
(rotate Port -> Land)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4050fb40}]
-- Landscape --
Previously focused view reported id 16908298 during save, but can't be found during restore.
(rotate Land -> Port)
ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@40528c60}]
-- Portrait --
ListFrag.onCreate(): null
getRetainInstance = false
当屏幕不断旋转时,创建的 Fragment 数量不会无限增加,我无法解释.(请帮忙)
where the number of Fragment created will not increase infinitely as screen keep rotating, which i cannot explain. (please help)
推荐答案
我终于想出了一个解决方案,以防止在 Activity 重新创建时重新创建不需要的 Fragment.这就像我在问题中提到的:
I finally come up with a solution to prevent unwanted Fragment re-creating upon the Activity re-creates. It is like what i mentioned in the question:
我发现的唯一巧妙的方法是在 中执行
.但这会带来另一个问题...getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();
onSaveInstanceState()
The only neat way i found is doing
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();
inonSaveInstanceState()
. But it will raise another problems...
...有一些改进.
在onSaveInstanceState()
中:
@Override
protected void onSaveInstanceState(Bundle outState) {
if (isPortrait2Landscape()) {
remove_fragments();
}
super.onSaveInstanceState(outState);
}
private boolean isPortrait2Landscape() {
return isDevicePortrait() && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
}
和 isDevicePortrait()
会像:
private boolean isDevicePortrait() {
return (findViewById(R.id.A_View_Only_In_Portrait) != null);
}
*请注意,我们不能使用getResources().getConfiguration().orientation
来确定设备是否当前是字面上的 肖像.这是因为 Resources
对象在RIGHT AFTER 屏幕旋转后发生了变化 - EVEN BEFORE> onSaveInstanceState()
被调用!!
*Notice that we cannot use getResources().getConfiguration().orientation
to determine if the device is currently literally Portrait. It is because the Resources
object is changed RIGHT AFTER the screen rotates - EVEN BEFORE onSaveInstanceState()
is called!!
如果我们不想使用 findViewById()
来测试方向(出于任何原因,毕竟它不是那么整洁),请保留一个全局变量 private int current_orientation;
并在 onCreate()
中通过 current_orientation = getResources().getConfiguration().orientation;
初始化它.这看起来更整洁.但是我们应该注意不要在 Activity 生命周期中的任何地方更改它.
If we do not want to use findViewById()
to test orientation (for any reasons, and it's not so neat afterall), keep a global variable private int current_orientation;
and initialise it by current_orientation = getResources().getConfiguration().orientation;
in onCreate()
. This seems neater. But we should be aware not to change it anywhere during the Activity lifecycle.
*确保我们remove_fragments()
之前 super.onSaveInstanceState()
.
(因为在我的情况下,我从布局和活动中删除了片段.如果在 super.onSaveInstanceState()
之后,布局将已经保存到捆绑.然后在重新创建活动后,片段将也重新创建.###)
(Because in my case, i remove the Fragments from the Layout, and from the Activity. If it is after super.onSaveInstanceState()
, the Layout will already be saved into the Bundle. Then the Fragments will also be re-created after the Activity re-creates. ###)
### 我已经证明了这个现象.但是要做什么的原因在重新创建活动时确定片段恢复? 只是我的猜测.如果您对此有任何想法,请回答我的另一个问题.
### I have proved this phenomenon. But the reason of What to determine a Fragment restore upon Activity re-create? is just by my guess. If you have any ideas about it, please answer my another question.
这篇关于处理屏幕旋转上的片段重复(带有示例代码)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!