Двойной фрагмент, вращающий Android с помощью ActionBar

Я сделал простую активность Android с панелью действий для переключения между двумя фрагментами. Все ок, пока я не переверну устройство. На самом деле, когда я поворачиваюсь, у меня есть 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);
    }
}

Прослушиватель вкладок

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);
        }
    }
}

person asclepix    schedule 26.02.2012    source источник


Ответы (4)


Я решил использовать onSaveInstanceState и onRestoreInstanceState в действии, чтобы сохранить выбранную вкладку и изменить onTabSelected следующим образом.

Последнее изменение позволяет избежать того, чтобы Android перестраивал фрагмент, который он не уничтожает. Однако я не понимаю, почему действие уничтожается событием вращения, а текущий фрагмент нет. (Имеешь ли ты какое-то представление об этом?)

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);
        }
    }
person asclepix    schedule 26.02.2012
comment
Это сработало отлично. Любая подсказка относительно того, является ли это правильным способом ведения дел? - person Miguel Moll; 05.04.2012
comment
Пока я не нашел это, это сводило меня с ума... Большое спасибо за этот ответ! (Все еще не могу поверить, что Google не исправит это в своем примере) - person Patrick; 16.10.2012
comment
Интересный. Только я использую android.support.v4.view.ViewPager в портрете и отображаю оба фрагмента в ландшафте. Поверните дважды, и я получу два фрагмента ландшафта. Один называется что-то вроде android:switcher:2131099773:1 — сводит меня с ума. Я должен найти скакательный сустав, чтобы избавиться от зомби. - person Martin; 21.12.2012
comment
После долгих размышлений это тоже решило проблему :) спасибо @asclepix - person Shiv; 31.01.2013
comment
Я этого не понимаю. onDestroy() действительно вызывается для фрагмента при вращении устройства. Как его все еще можно использовать и видеть после этого? - person Nilzor; 17.03.2013
comment
Работает как шарм! Кто-нибудь знает, почему пример из официального документа не работает? Мы все делаем что-то не так? - person ValarDohaeris; 30.10.2014

Поскольку я использую 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
person Martin    schedule 21.12.2012
comment
Это работало хорошо, за исключением того, что когда фрагмент 2 переходит в альбомную ориентацию, отображается фрагмент 1. Не уверен, что я единственный, у кого есть эта проблема. Любое решение? - person Art; 24.04.2013
comment
Работает с вашим решением в портретной и альбомной ориентации. больше не перекрываются после переключения между вкладками. - person Alexandru Circus; 24.05.2013

Решение на самом деле не требует большой работы, следующие шаги гарантируют, что при повороте экрана выбор вкладки сохраняется. Я столкнулся с перекрывающимися фрагментами, потому что при повороте экрана была выбрана моя первая вкладка, а не вторая, которая была выбрана до поворота экрана, и, следовательно, первая вкладка перекрывала содержимое второй вкладки.

Вот как должна выглядеть ваша активность (я использую 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
     }

Приведенная выше реализация также позволяет заменять фрагменты на вкладке на основе их тегов. Для этого при переключении фрагментов в пределах одной вкладки я использую то же имя тега, которое использовалось для исходного фреймворка, добавленного на вкладку.

person AgentKnopf    schedule 08.03.2013

Спасибо Мартину и 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, 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'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.
    }
}
person andep    schedule 27.04.2013