Двойной фрагмент, вращающийся Android с ActionBar
Я сделал простую активность Android с помощью ActionBar для переключения между двумя фрагментами.
Все это нормально, пока я не повернул устройство. В фактах, когда я вращаюсь, у меня есть 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 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
Я решил использовать onSaveInstanceState
и onRestoreInstanceState
в Управлении для сохранения выбранной вкладки и изменения onTabSelected
следующим образом.
Последнее изменение избегает того, что Android восстанавливает фрагмент, который он не уничтожает. Однако я не понимаю, почему активность разрушена событием вращения, а текущий фрагмент no. (У вас есть представление об этом?)
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);
}
}
Ответ 2
Поскольку я использую android.support.v4.view.ViewPager
переопределение onTabSelected
, это не поможет. Но все же ты намекнул мне в правильном направлении.
android.support.v4.app.FragmentManager
сохраняет все фрагменты в onSaveInstanceState
android.support.v4.app.FragmentActivity
. Игнорирование setRetainInstance
. В зависимости от вашего дизайна это может привести к дублированию фрагментов.
Простейшим решением является удаление сохраненного фрагмента в orCreate
активности:
@Override
public void onCreate (final android.os.Bundle savedInstanceState)
{
if (savedInstanceState != null)
{
savedInstanceState.remove ("android:support:fragments");
} // if
super.onCreate (savedInstanceState);
…
return;
} // onCreate
Ответ 3
Решение на самом деле не требует большой работы, следующие шаги гарантируют, что при повороте экрана сохраняется вкладка. Я столкнулся с перекрывающимися фрагментами, потому что при вращении экрана была выбрана первая вкладка, а не вторая, которая была выбрана до поворота экрана, и, следовательно, первая вкладка перекрывала содержимое второй вкладки.
Вот как должна выглядеть ваша деятельность (я использую 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(его частный класс в вышеуказанной деятельности):
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_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<FrameLayout
android:id="@+id/frActiveTask"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
/>
<FrameLayout
android:id="@+id/frTaskList"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_above="@id/frActiveTask"
/>
</RelativeLayout>
Использование методов и операторов onRestoreInstanceState
, onSaveInstanceState
и savedInstanceState.remove("android:support:fragments");
работает почти отлично, за исключением того, что если активная вкладка не является первой и вращается и сначала нажимает, появляется ясный экран и только для второго щелчка на на первой вкладке появился нужный фрагмент.
После отладки кода я узнал, что первый addTab
всегда вызывает событие onTabSelected
в прослушивателе вкладок с помощью метода add
, а затем, когда вызывается setSelectedNavigationItem
из onRestoreInstanceState
a 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());
}
И измененный прослушиватель вкладки
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 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.
}
}