2013-08-19 3 views
104

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

Декларирование и инициализация:

Fragment A = new AFragment(); 
Fragment B = new BFragment(); 
Fragment C = new CFragment(); 

Замена/Добавление:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 
ft.replace(R.id.content_frame, A); 
ft.addToBackStack(null); 
ft.commit(); 

Эти фрагменты работают должным образом. Каждый фрагмент привязан к действию и сохраняется без проблем.

Так что, когда я запускаю A, C, а затем B, стек выглядит следующим образом:

| | 
|B| 
|C| 
|A| 
___ 

И когда я нажимаю кнопку «назад», B разрушается и C возобновляется.

Но, когда я запускаю фрагментировать A во второй раз, вместо того, чтобы возобновить с заднего стеком, он добавляется в верхней части заднего стека

| | 
|A| 
|C| 
|A| 
___ 

Но я хочу возобновить A и уничтожить все фрагменты на сверху (если есть). На самом деле, мне нравится поведение по умолчанию в фоновом режиме.

Как это сделать?

Ожидаемое: (A должна быть возобновлена ​​и верхние фрагменты должны быть уничтожены)

| | 
| | 
| | 
|A| 
___ 

Edit: (предложенный A - C)

Это мой код пытается:

private void selectItem(int position) { 
     Fragment problemSearch = null, problemStatistics = null; 
     FragmentManager manager = getSupportFragmentManager(); 
     FragmentTransaction ft = manager.beginTransaction(); 
     String backStateName = null; 
     Fragment fragmentName = null; 
     boolean fragmentPopped = false; 
     switch (position) { 
     case 0: 
      fragmentName = profile; 
      break; 
     case 1: 
      fragmentName = submissionStatistics; 
      break; 
     case 2: 
      fragmentName = solvedProblemLevel; 
      break; 
     case 3: 
      fragmentName = latestSubmissions; 
      break; 
     case 4: 
      fragmentName = CPExercise; 
      break; 
     case 5: 
      Bundle bundle = new Bundle(); 
      bundle.putInt("problem_no", problemNo); 
      problemSearch = new ProblemWebView(); 
      problemSearch.setArguments(bundle); 
      fragmentName = problemSearch; 
      break; 
     case 6: 
      fragmentName = rankList; 
      break; 
     case 7: 
      fragmentName = liveSubmissions; 
      break; 
     case 8: 
      Bundle bundles = new Bundle(); 
      bundles.putInt("problem_no", problemNo); 
      problemStatistics = new ProblemStatistics(); 
      problemStatistics.setArguments(bundles); 
      fragmentName = problemStatistics; 
     default: 
      break; 
     } 
     backStateName = fragmentName.getClass().getName(); 
     fragmentPopped = manager.popBackStackImmediate(backStateName, 0); 
     if (!fragmentPopped) { 
      ft.replace(R.id.content_frame, fragmentName); 
     } 
     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 
     ft.addToBackStack(backStateName); 
     ft.commit(); 

     // I am using drawer layout 
     mDrawerList.setItemChecked(position, true); 
     setTitle(title[position]); 
     mDrawerLayout.closeDrawer(mDrawerList); 
    } 

Проблема заключается, когда я запускаю A, а затем B, а затем нажмите 'назад', B удаляется и возобновляется A. и нажатие «назад» второй раз должно выйти из приложения. Но он показывает пустое окно, и я должен нажать в третий раз, чтобы закрыть его.

Кроме того, когда я запускаю A, затем B, затем C, затем B снова ...

Ожидаемое:

| | 
| | 
|B| 
|A| 
___ 

Actual:

| | 
|B| 
|B| 
|A| 
___ 

Должен ли я заменить onBackPressed() с любая настройка или я что-то упускаю?

ответ

220

Считывание documentation, есть способ поместить задний стек на основе либо имени транзакции, либо идентификатора, предоставленного фиксацией.Использование имени может быть проще, поскольку оно не требует отслеживания числа, которое может меняться и подкрепляет логику «уникальной обратной записи стека».

Поскольку вы хотите получить только одну запись заднего стека за Fragment, сделайте имя состояния задней части имени класса фрагмента (через getClass().getName()). Затем при замене Fragment используйте метод popBackStackImmediate(). Если он возвращает true, это означает, что есть экземпляр фрагмента в фоновом стеке. Если нет, на самом деле выполните логику замены фрагментов.

private void replaceFragment (Fragment fragment){ 
    String backStateName = fragment.getClass().getName(); 

    FragmentManager manager = getSupportFragmentManager(); 
    boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0); 

    if (!fragmentPopped){ //fragment not in back stack, create it. 
    FragmentTransaction ft = manager.beginTransaction(); 
    ft.replace(R.id.content_frame, fragment); 
    ft.addToBackStack(backStateName); 
    ft.commit(); 
    } 
} 

EDIT

Проблема - когда я запускаю, а затем B, а затем нажмите кнопку назад, B удаляется и А возобновляется. и нажатие кнопки возврата назад должно выйти из приложения. Но он показывает пустое окно и нуждается в другом нажатии , чтобы закрыть его.

Это потому, что FragmentTransaction добавляется в задний стек, чтобы мы могли поместить фрагменты сверху позже. Быстрое исправление для этого перекрывая onBackPressed() и заканчивая активность, если стек обратно содержит только 1 Fragment

@Override 
public void onBackPressed(){ 
    if (getSupportFragmentManager().getBackStackEntryCount() == 1){ 
    finish(); 
    } 
    else { 
    super.onBackPressed(); 
    } 
} 

Относительно дублируют обратно записи стека, ваш условный оператор, который заменяет фрагмент, если он не всплывающую явно разные, чем мой оригинальный фрагмент кода. То, что вы делаете, заключается в добавлении в задний стек независимо от того, был ли выведен задний стек.

Что-то, как это должно быть ближе к тому, что вы хотите:

private void replaceFragment (Fragment fragment){ 
    String backStateName = fragment.getClass().getName(); 
    String fragmentTag = backStateName; 

    FragmentManager manager = getSupportFragmentManager(); 
    boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0); 

    if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it. 
    FragmentTransaction ft = manager.beginTransaction(); 
    ft.replace(R.id.content_frame, fragment, fragmentTag); 
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 
    ft.addToBackStack(backStateName); 
    ft.commit(); 
    } 
} 

Условный был немного изменился с момента выбора и тот же фрагмент в то время как он был виден также вызвал повторяющихся записей.

Реализация:

я настоятельно рекомендую не принимать обновленный replaceFragment() метод обособленно, как вы делали в вашем коде. Вся эта логика содержится в этом методе, и движущиеся части вокруг могут вызвать проблемы.

Это означает, что вы должны скопировать обновленный replaceFragment() метод в классе то изменить

backStateName = fragmentName.getClass().getName(); 
fragmentPopped = manager.popBackStackImmediate(backStateName, 0); 
if (!fragmentPopped) { 
      ft.replace(R.id.content_frame, fragmentName); 
} 
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 
ft.addToBackStack(backStateName); 
ft.commit(); 

так просто

replaceFragment (fragmentName); 

EDIT # 2

Для обновления ящик, когда изменяется задний стек, делает метод, который принимает во Фрагменте и сравнивает имена классов. Если что-то соответствует, измените заголовок и выбор. Также добавьте OnBackStackChangedListener и попросите его вызвать метод обновления, если есть действительный фрагмент.

Например, в данный вид деятельности onCreate(), добавить

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() { 

    @Override 
    public void onBackStackChanged() { 
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame); 
    if (f != null){ 
     updateTitleAndDrawer (f); 
    } 

    } 
}); 

И другой метод:

private void updateTitleAndDrawer (Fragment fragment){ 
    String fragClassName = fragment.getClass().getName(); 

    if (fragClassName.equals(A.class.getName())){ 
    setTitle ("A"); 
    //set selected item position, etc 
    } 
    else if (fragClassName.equals(B.class.getName())){ 
    setTitle ("B"); 
    //set selected item position, etc 
    } 
    else if (fragClassName.equals(C.class.getName())){ 
    setTitle ("C"); 
    //set selected item position, etc 
    } 
} 

Теперь, когда изменяется обратно стека, заголовок и проверяется положение будет отражать видимый Fragment ,

+0

Спасибо за ваш ответ. :) Он работает частично. Я редактировал свой вопрос с прогрессом. Пожалуйста, посмотрите) –

+0

@typedef Я сделаю образец проекта и посмотрю, смогу ли я воспроизвести вашу проблему. Я отредактирую вопрос/оставлю комментарий, если найду что-нибудь полезное. –

+0

Спасибо, сэр! Я жду этого :) –

3

Шаг 1: Реализовать интерфейс с классом активности

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{ 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     ............. 
     FragmentManager fragmentManager = getFragmentManager();   
     fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit(); 
    } 

    private void switchFragment(Fragment fragment){    
     FragmentManager fragmentManager = getFragmentManager(); 
     fragmentManager.beginTransaction() 
     .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit(); 
    } 

    @Override 
    public void onBackStackChanged() { 
    FragmentManager fragmentManager = getFragmentManager(); 

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
      + fragmentManager.getBackStackEntryCount()); 

    int count = fragmentManager.getBackStackEntryCount(); 

    // when a fragment come from another the status will be zero 
    if(count == 0){ 

     System.out.println("again loading user data"); 

     // reload the page if user saved the profile data 

     if(!objPublicDelegate.checkNetworkStatus()){ 

      objPublicDelegate.showAlertDialog("Warning" 
        , "Please check your internet connection"); 

     }else { 

      objLoadingDialog.show("Refreshing data..."); 

      mNetworkMaster.runUserSummaryAsync(); 
     } 

     // IMPORTANT: remove the current fragment from stack to avoid new instance 
     fragmentManager.removeOnBackStackChangedListener(this); 

    }// end if 
    }  
} 

Шаг 2: При вызове другой фрагмент добавить этот метод:

String backStateName = this.getClass().getName(); 

FragmentManager fragmentManager = getFragmentManager(); 
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment(); 
Bundle bundle = new Bundle(); 
bundle.putString("graphTag", view.getTag().toString()); 
fragmentGraph.setArguments(bundle); 

fragmentManager.beginTransaction() 
.replace(R.id.content_frame, fragmentGraph) 
.addToBackStack(backStateName) 
.commit(); 
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { 

    @Override 
    public void onBackStackChanged() { 

     if(getFragmentManager().getBackStackEntryCount()==0) { 
      onResume(); 
     }  
    } 
}); 
1

Я думаю, этот метод мой решить вашу проблему:

public static void attachFragment (int fragmentHolderLayoutId, Fragment fragment, Context context, String tag) { 


    FragmentManager manager = ((AppCompatActivity) context).getSupportFragmentManager(); 
    manager.findFragmentByTag (tag); 
    FragmentTransaction ft = manager.beginTransaction(); 

    if (manager.findFragmentByTag (tag) == null) { // No fragment in backStack with same tag.. 
     ft.add (fragmentHolderLayoutId, fragment, tag); 
     ft.addToBackStack (tag); 
     ft.commit(); 
    } 
    else { 
     ft.show (manager.findFragmentByTag (tag)).commit(); 
    } 
} 

, которая первоначально была опубликована в This Question

+0

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