2011-07-22 2 views
123

В андроиде фрагмент (скажем, FragA) добавляется в заднюю часть, а другой фрагмент (скажем, FragB) подходит к вершине. Теперь на ударе назад FragA выходит на вершину и вызывается onCreateView(). Теперь у меня было FragA в определенном состоянии до того, как FragB натолкнулся на него сверху.андроидный фрагмент. Как сохранить состояния представлений в фрагменте, когда другой фрагмент нажат поверх него.

Вопрос: как я могу восстановить FragA в предыдущее состояние? Есть ли способ сохранить состояние (например, сказать в Bundle), и если да, то какой метод я должен переопределить?

ответ

97

В fragment guide FragmentList примере вы можете найти:

@Override 
public void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 
    outState.putInt("curChoice", mCurCheckPosition); 
} 

Что вы можете использовать позже, как это:

@Override 
public void onActivityCreated(Bundle savedInstanceState) { 
    super.onActivityCreated(savedInstanceState); 
    if (savedInstanceState != null) { 
     // Restore last state for checked position. 
     mCurCheckPosition = savedInstanceState.getInt("curChoice", 0); 
    } 
} 

Я новичок в осколках, но кажется, что решение вашей проблемы;) OnActivityCreated вызывается после возврата фрагмента из предыдущего стека.

+0

да что кажется хорошей идеей. Попробуй это. Спасибо – pankajagarwal

+17

Я не мог заставить это работать savedInstanceState всегда был нулевым. Я добавляю фрагмент через макет xml. Если бы изменить mCurCheckPosition на статический, то он работает, но чувствует себя взломанным. – scottyab

+0

@scottyab у вашего фрагмента есть id (если нет, он должен). – ania

4

если вы регулируете изменения конфигурации в вашем фрагменте деятельности, указанные в андроиде манифеста как этого

<activity 
    android:name=".courses.posts.EditPostActivity" 
    android:configChanges="keyboardHidden|orientation" 
    android:screenOrientation="unspecified" /> 

затем onSaveInstanceState фрагмента не будет ссылаться а savedInstanceState объекта всегда будет нулевым.

8

Я работал с проблемой, очень похожей на это. Поскольку я знал, что часто возвращаюсь к предыдущему фрагменту, я проверил, действительно ли был фрагмент .isAdded(), и если да, то вместо того, чтобы делать transaction.replace(), я просто делаю transaction.show(). Это позволяет восстановить фрагмент, если он уже находится в стеке - не требуется сохранение состояния.

Fragment target = <my fragment>; 
FragmentTransaction transaction = getFragmentManager().beginTransaction(); 
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 
if(target.isAdded()) { 
    transaction.show(target); 
} else { 
    transaction.addToBackStack(button_id + "stack_item"); 
    transaction.replace(R.id.page_fragment, target); 
} 
transaction.commit(); 

Другое дело, иметь в виду, что в то время как это сохраняет естественный порядок для фрагментов себя, вы, возможно, все еще нужно обрабатывать деятельность сама разрушается и воссозданный по ориентации (конфигурации) изменения. Чтобы обойти эту проблему в AndroidManifest.xml для узла:

android:configChanges="orientation|screenSize" 

В Android 3.0 и выше, то, по-видимому screenSize требуется.

Успехов

+0

transaction.addToBackStack (button_id + "stack_item"); // что делает эта строка.Что такое button_id здесь? –

+0

button_id - это просто составленная переменная. Аргумент string, переданный в addToBackStack, является просто необязательным именем для состояния backstack - вы можете установить его равным null, если вы управляете только одной загрузкой. – rmirabelle

+0

, у меня проблема, подобная этому, с фрагментами, можете ли вы заглянуть в нее? Http: //stackoverflow.com/questions/22468977/android-fragment-state-restored-only-on-back-button-not- when-i-select-a-fragme –

20

Просто обратите внимание, что если вы работаете с использованием фрагментов ViewPager, это довольно легко. Вам нужно только позвонить по этому методу: setOffscreenPageLimit().

Accordign к документации:

Установите количество страниц, которые должны быть сохранены в обе стороны от текущей страницы в иерархии вид в неактивном состоянии. Страницы, выходящие за этот предел, будут воссозданы из адаптера, когда это необходимо.

Simmilar issue here

+4

Это другое , setOffScreenPageLimit действует как кеш (это означает, сколько страниц должен обрабатывать ViewPager в данный момент), но не используется для сохранения состояния фрагмента. –

+0

В моем случае он работал с setOffscreenPageLimit() - хотя фрагменты были уничтожены, состояние просмотра было сохранено и восстановлено. – Davincho

+0

работал для меня со скрытой навигацией –

70

фрагмента onSaveInstanceState(Bundle outState) никогда не будет вызываться, если активность фрагмента не называть его на себе и прикрепленных фрагментов.Таким образом, этот метод не будет вызываться до тех пор, пока что-то (обычно вращение) не активирует активность до SaveInstanceState и не восстановит его позже. Но если у вас есть только одна активность и большой набор фрагментов внутри нее (с интенсивным использованием replace), и приложение запускается только в одной ориентации, onSaveInstanceState(Bundle outState) может не вызываться в течение длительного времени.

Я знаю три возможных обходных пути.

Первое: аргументы

использование фрагмента для хранения важных данных:

public class FragmentA extends Fragment { 
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable"; 

    private EditText persistentVariableEdit; 

    public FragmentA() { 
     setArguments(new Bundle()); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     View view = inflater.inflate(R.layout.fragment_a, null); 

     persistentVariableEdit = (EditText) view.findViewById(R.id.editText); 

     TextView proofTextView = (TextView) view.findViewById(R.id.textView); 

     Bundle mySavedInstanceState = getArguments(); 
     String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY); 

     proofTextView.setText(persistentVariable); 


     view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       getFragmentManager() 
         .beginTransaction() 
         .replace(R.id.frameLayout, new FragmentB()) 
         .addToBackStack(null) 
         .commit(); 
      } 
     }); 

     return view; 
    } 

    @Override 
    public void onPause() { 
     super.onPause(); 
     String persistentVariable = persistentVariableEdit.getText().toString(); 

     getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable); 
    } 
} 

Второй, но менее педантичный способ - владения переменных одиночек

Третьего - Не replace() фрагментов но add()/show()/hide() их вместо.

+3

Лучшее решение, когда 'Fragment.onSaveInstanceState()' никогда не вызывалось. Просто сохраните свои собственные данные в аргументе, включая элементы в представлении списка или только их идентификаторы (если у вас есть другой централизованный менеджер данных). Нет необходимости сохранять позицию списка просмотра - это было автоматически сохранено и восстановлено. –

+0

Я попытался использовать ваш пример в своем приложении, но это: 'String persistentVariable = mySavedInstanceState.getString (PERSISTENT_VARIABLE_BUNDLE_KEY);' всегда 'null'. В чем проблема? – fragon

+0

Использовать фрагмент 'getArguments()' DEFINITELY - путь, включая вложенные фрагменты в ViewPager. Я использую 1 активность и меняю много фрагментов в/из, и это работает отлично. Вот простой тест для вас, чтобы проверить любое предлагаемое решение: 1) перейти от фрагмента A к фрагменту B; 2) дважды измените ориентацию устройства; 3) нажмите кнопку «Назад» на устройстве. –

-1
private ViewPager viewPager; 
viewPager = (ViewPager) findViewById(R.id.pager); 
mAdapter = new TabsPagerAdapter(getSupportFragmentManager()); 
viewPager.setAdapter(mAdapter); 
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 

     @Override 
     public void onPageSelected(int position) { 
      // on changing the page 
      // make respected tab selected 
      actionBar.setSelectedNavigationItem(position); 
     } 

     @Override 
     public void onPageScrolled(int arg0, float arg1, int arg2) { 
     } 

     @Override 
     public void onPageScrollStateChanged(int arg0) { 
     } 
    }); 
} 

@Override 
public void onTabReselected(Tab tab, FragmentTransaction ft) { 
} 

@Override 
public void onTabSelected(Tab tab, FragmentTransaction ft) { 
    // on tab selected 
    // show respected fragment view 
    viewPager.setCurrentItem(tab.getPosition()); 
} 

@Override 
public void onTabUnselected(Tab tab, FragmentTransaction ft) { 
} 
+5

Пожалуйста, включите некоторую информацию о вашем ответе, а не просто отправьте код. Мы стараемся предоставлять не только «исправления», но и помогать людям учиться. Вы должны объяснить, что было не так в исходном коде, что вы сделали по-другому и почему ваши изменения (ы) работали. –

0

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

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) { 
    FragmentManager fragmentManager = getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentManager.beginTransaction(); 
    transaction.setCustomAnimations(0,0,0,0); 
    transaction.hide(currentFragment); 
    // use a fragment tag, so that later on we can find the currently displayed fragment 
    transaction.add(R.id.frame_layout, targetFragment, tag) 
      .addToBackStack(tag) 
      .commit(); 
} 

Я использую этот метод в моем фрагменте (содержащий в виде списка) всякий раз, когда элемент списка щелкнул/похлопал (и, таким образом, мне нужно, чтобы запустить/дисплей подробност фрагмент):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); 
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]); 
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details"); 
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details"); 

getFragmentTags() возвращает массив строк, которые я использую в качестве тегов для различных фрагментов при добавлении нового фрагмента (см transaction.add метода в addFragment выше способом).

В фрагмент, содержащий представление списка, я делаю это в методе OnPause():

@Override 
public void onPause() { 
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one 
    ListView lv = (ListView) getActivity().findViewById(R.id.listView); 
    mListViewState = lv.onSaveInstanceState(); 
    super.onPause(); 
} 

Затем в onCreateView фрагмента (на самом деле в методе, который вызывается в onCreateView), я восстановить состояние:

// Restore previous state (including selected item index and scroll position) 
if(mListViewState != null) { 
    Log.d(TAG, "Restoring the listview's state."); 
    lv.onRestoreInstanceState(mListViewState); 
} 
5

лучшее решение я нашел ниже:

onSavedInstanceState(): всегда вызывается внутри фрагмента, когда активность будет закрыта (Move activi от одного к другому или изменения конфигурации). Итак, если мы вызываем несколько фрагментов при одинаковой активности, мы должны использовать следующий подход:

Используйте OnDestroyView() фрагмента и сохраните весь объект внутри этого метода. Затем OnActivityCreated(): Убедитесь, что если объект имеет значение null или нет (поскольку этот метод вызывает каждый раз). Теперь восстановите состояние объекта здесь.

Его работы всегда!

+1

привет !! @ amanGoel Могу ли вы помочь с некоторым примером кода. – UchihaSasuke

0

i donot think onSaveInstanceState - хорошее решение. он просто использует для деятельности, которая была разрушена.

От android 3.0 Fragmen был менеджером FragmentManager, условием является: одно действие, отображающее фрагменты manny, когда фрагмент добавлен (не заменяется: он будет воссоздан) в backStack, представление будет уничтожено. когда он возвращается к последнему, он будет отображаться, как и раньше.

Итак, я думаю, что documentManager и транзакция достаточно хороши для его обработки.

0

В конце концов, попробовав многие из этих сложных решений, поскольку мне нужно было сохранить/восстановить только одно значение в моем фрагменте (содержимое EditText), и хотя это может быть не самое элегантное решение, создание SharedPreference и хранение моего состояния там работало для меня

10

Просто надуйте свой взгляд на этот раз.

Exemple следующим образом:

public class AFragment extends Fragment { 

private View mRootView; 
@Nullable 
@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
    if(mRootView==null){ 
     mRootView = inflater.inflate(R.id.fragment_a, container, false); 
     //...... 
    } 
    return mRootView; 
} 

}

+0

также должен содержать существующие фрагменты в массиве или что-то такое. – Amir

+0

Я слышал, что сохранение ссылки на фрагмент rootView - это плохая практика - может привести к утечкам [править]? –

+0

@ giraffe.guru «Фрагмент» относится к его корневому представлению, это не сделает. Хотя ссылка на некоторые ** GC-корневые элементы ** будет, как и глобальное статическое свойство, переменной non-ui. Экземпляр «Фрагмент» не является GC-root, поэтому его можно собрать мусором. Так будет и его корневой вид. –

Смежные вопросы