使用 ActionBar 旋转 Android 的双片段
Posted
技术标签:
【中文标题】使用 ActionBar 旋转 Android 的双片段【英文标题】:Double fragment rotating Android with ActionBar 【发布时间】:2012-03-16 03:39:45 【问题描述】:我制作了一个带有 ActionBar 的简单 android Activity,用于在 2 个片段之间切换。 在我旋转设备之前一切正常。事实上,当我旋转时,我有 2 个片段一个接一个:前一个活动片段和第一个片段。 为什么? 如果旋转破坏并重新创建我的活动,为什么我会获得 2 个片段?
示例代码:
活动
package rb.rfrag.namespace;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.os.Bundle;
public class RFragActivity extends Activity
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
// Notice that setContentView() is not used, because we use the root
// android.R.id.content as the container for each fragment
// setup action bar for tabs
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//actionBar.setDisplayShowTitleEnabled(false);
Tab tab;
tab = actionBar.newTab()
.setText(R.string.VarsTab)
.setTabListener(new TabListener<VarValues>(
this, "VarValues", VarValues.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.SecTab)
.setTabListener(new TabListener<SecFrag>(
this, "SecFrag", SecFrag.class));
actionBar.addTab(tab);
TabListener
package rb.rfrag.namespace;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
public class TabListener<T extends Fragment> implements ActionBar.TabListener
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** Constructor used each time a new tab is created.
* @param activity The host Activity, used to instantiate the fragment
* @param tag The identifier tag for the fragment
* @param clz The fragment's Class, used to instantiate the fragment
*/
public TabListener(Activity activity, String tag, Class<T> clz)
mActivity = activity;
mTag = tag;
mClass = clz;
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft)
// Check if the fragment is already initialized
if (mFragment == null)
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
else
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
public void onTabUnselected(Tab tab, FragmentTransaction ft)
if (mFragment != null)
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
【问题讨论】:
【参考方案1】:我已经解决了在Activity中使用onSaveInstanceState
和onRestoreInstanceState
来维护选中的tab,修改onTabSelected
如下。
最后的修改避免了 Android 重建它不会破坏的 Fragment。但是我不明白为什么 Activity 被旋转事件破坏,而当前的 Fragment 没有。 (你有什么想法吗?)
public void onTabSelected(Tab tab, FragmentTransaction ft)
// previous Fragment management
Fragment prevFragment;
FragmentManager fm = mActivity.getFragmentManager();
prevFragment = fm.findFragmentByTag(mTag);
if (prevFragment != null)
mFragment = prevFragment;
// \previous Fragment management
// Check if the fragment is already initialized
if (mFragment == null)
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
else
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
【讨论】:
这非常有效。关于这是否是“正确”的做事方式的任何线索? 直到我发现这个,这让我发疯了......非常感谢这个答案! (仍然无法相信谷歌不会在他们的例子中解决这个问题) 有趣。只有我在纵向使用android.support.v4.view.ViewPager
并在横向显示两个片段。旋转两次,我得到两个横向片段。一个叫做 android:switcher:2131099773:1 的东西——让我抓狂。我必须找到一个飞节来摆脱僵尸。
经过大量的 r 和 d 这也解决了 mah 问题:)thanx @asclepix
我不明白这个。旋转设备时,确实在片段上调用了 onDestroy()。在那之后它怎么还能被使用和可见?【参考方案2】:
由于我使用 android.support.v4.view.ViewPager
覆盖 onTabSelected
将无济于事。但是你仍然暗示我指向了正确的方向。
android.support.v4.app.FragmentManager
将所有片段保存在android.support.v4.app.FragmentActivity
的onSaveInstanceState
中。 忽略setRetainInstance
— 根据您的设计,这可能会导致重复片段。
最简单的解决方法是删除activity的orCreate
中保存的fragment:
@Override
public void onCreate (final android.os.Bundle savedInstanceState)
if (savedInstanceState != null)
savedInstanceState.remove ("android:support:fragments");
// if
super.onCreate (savedInstanceState);
…
return;
// onCreate
【讨论】:
这很好,只是当 Fragment2 进入 Landscape 时,显示 fragment1。不知道我是不是唯一一个有这个问题的人。有什么解决办法吗? 也适用于您的纵向和横向解决方案。在标签之间切换后不再重叠。【参考方案3】:该解决方案实际上并不需要大量工作,以下步骤可确保在旋转屏幕时保持选项卡选择。我遇到了重叠的片段,因为在屏幕旋转时选择了我的第一个选项卡,而不是在旋转屏幕之前选择的第二个选项卡,因此第一个选项卡与第二个选项卡的内容重叠。
这就是您的 Activity 的外观(我正在使用 ActionBarSherlock,但调整它应该很容易):
public class TabHostActivity extends SherlockFragmentActivity
private static final String SELETED_TAB_INDEX = "tabIndex";
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
// Setup the action bar
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Create the Tabs you need and add them to the actionBar...
if (savedInstanceState != null)
// Select the tab that was selected before orientation change
int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
actionBar.setSelectedNavigationItem(index);
@Override
protected void onSaveInstanceState(Bundle outState)
super.onSaveInstanceState(outState);
// Save the index of the currently selected tab
outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
这就是我的 ActionBar.TabListener 的样子(它是上述 Activity 中的私有类):
private class MyTabsListener<T extends Fragment> implements ActionBar.TabListener
private Fragment fragment;
private final SherlockFragmentActivity host;
private final Class<Fragment> type;
private String tag;
public MyTabsListener(SherlockFragmentActivity parent, String tag, Class type)
this.host = parent;
this.tag = tag;
this.type = type;
@Override
public void onTabSelected(Tab tab, FragmentTransaction transaction)
/*
* The fragment which has been added to this listener may have been
* replaced (can be the case for lists when drilling down), but if the
* tag has been retained, we should find the actual fragment that was
* showing in this tab before the user switched to another.
*/
Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
// Check if the fragment is already initialised
if (currentlyShowing == null)
// If not, instantiate and add it to the activity
fragment = SherlockFragment.instantiate(host, type.getName());
transaction.add(android.R.id.content, fragment, tag);
else
// If it exists, simply attach it in order to show it
transaction.attach(currentlyShowing);
public void onTabUnselected(Tab tab, FragmentTransaction fragmentTransaction)
/*
* The fragment which has been added to this listener may have been
* replaced (can be the case for lists when drilling down), but if the
* tag has been retained, we should find the actual fragment that's
* currently active.
*/
Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
if (currentlyShowing != null)
// Detach the fragment, another tab has been selected
fragmentTransaction.detach(currentlyShowing);
else if (this.fragment != null)
fragmentTransaction.detach(fragment);
public void onTabReselected(Tab tab, FragmentTransaction fragmentTransaction)
// This tab is already selected
上述实现还允许根据标签替换选项卡中的片段。为此,在同一选项卡中切换片段时,我使用相同的标记名称,该名称用于添加到选项卡的初始框架。
【讨论】:
【参考方案4】:感谢 Martin 和 asclepix 提供的解决方案。我有 3 个选项卡,第一个选项卡包含 2 个片段,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
tools:context=".MainActivity" >
<FrameLayout
android:id="@+id/frActiveTask"
android:layout_
android:layout_
android:layout_alignParentBottom="true"
/>
<FrameLayout
android:id="@+id/frTaskList"
android:layout_
android:layout_
android:layout_alignParentTop="true"
android:layout_above="@id/frActiveTask"
/>
</RelativeLayout>
使用onRestoreInstanceState
、onSaveInstanceState
和savedInstanceState.remove("android:support:fragments");
方法和语句几乎可以正常工作,除非您的活动选项卡不是第一个并旋转并首先单击,会出现清晰的显示,并且仅在第二次单击第一个选项卡是正确的片段显示。
调试代码后,我发现第一个addTab
总是在选项卡侦听器中调用onTabSelected
事件,并使用片段add
方法,然后当从onRestoreInstanceState
调用setSelectedNavigationItem
时,将执行detach
第一个选项卡和 add
另一个选项卡。
这个不必要的add
调用已在我的解决方案中修复。
我的活动
protected void onCreate(Bundle savedInstanceState)
boolean firstTabIsNotAdded = false;
if (savedInstanceState != null)
savedInstanceState.remove("android:support:fragments");
firstTabIsNotAdded = savedInstanceState.getInt(SELETED_TAB_INDEX) != 0;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// codes before adding tabs
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
tabStartAndStop = actionBar.newTab().setText(getString(R.string.tab_title_start_and_stop))
.setTabListener(
new FragmentTabListener<StartStopFragment>(this,
getString(R.string.tab_title_start_and_stop_id),
StartStopFragment.class,
firstTabIsNotAdded));
tabHistory = actionBar.newTab().setText(getString(R.string.tab_title_history))
.setTabListener(
new FragmentTabListener<HistoryFragment>(this,
getString(R.string.tab_title_history_id),
HistoryFragment.class,
false));
tabRiporting = actionBar.newTab().setText(getString(R.string.tab_title_reporting))
.setTabListener(
new FragmentTabListener<ReportingFragment>(this,
getString(R.string.tab_title_reporting_id),
ReportingFragment.class,
false));
actionBar.addTab(tabStartAndStop);
actionBar.addTab(tabHistory);
actionBar.addTab(tabRiporting);
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
if (savedInstanceState != null)
int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
actionBar.setSelectedNavigationItem(index);
super.onRestoreInstanceState(savedInstanceState);
@Override
protected void onSaveInstanceState(Bundle outState)
super.onSaveInstanceState(outState);
// Save the index of the currently selected tab
outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
以及修改后的tab监听器
public class FragmentTabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener
private Fragment mFragment;
private final Activity mFragmentActivity;
private final String mTag;
private final Class<T> mClass;
private boolean doNotAdd;
/** Constructor used each time a new tab is created.
* @param activity The host Activity, used to instantiate the fragment
* @param tag The identifier tag for the fragment
* @param clz The fragment's Class, used to instantiate the fragment
*/
public FragmentTabListener(Activity activity, String tag, Class<T> clz, boolean doNotAdd)
mFragmentActivity = activity;
mTag = tag;
mClass = clz;
this.doNotAdd = doNotAdd;
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft)
// Check if the fragment is already initialized
if (mFragment == null)
// If not, instantiate and add it to the activity
if(doNotAdd)
doNotAdd = false;
else
mFragment = Fragment.instantiate(mFragmentActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
else
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
public void onTabUnselected(Tab tab, FragmentTransaction ft)
if (mFragment != null)
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
public void onTabReselected(Tab tab, FragmentTransaction ft)
// User selected the already selected tab. Usually do nothing.
【讨论】:
以上是关于使用 ActionBar 旋转 Android 的双片段的主要内容,如果未能解决你的问题,请参考以下文章