как возобновить фрагмент из backstack, если существует

<p>я учусь использовать фрагменты. у меня есть три экземпляра <code>fragment</code>, которые инициализируются в верхней части класса. я добавляю фрагмент к деятельности, как это:</p><p>объявление и инициализация:</p><pre><code>fragment a = new afragment(); fragment b = new bfragment(); fragment c = new cfragment(); </code></pre><p>замена/добавление:</p><pre><code>fragmenttransaction ft = getsupportfragmentmanager().begintransaction(); ft.replace(r.id.content\_frame, a); ft.addtobackstack(null); ft.commit(); </code></pre><p>эти фрагменты работают правильно. каждый фрагмент прикреплен к действию и без проблем сохраняется в заднем стеке.</p><p>поэтому, когда я запускаю <code>a</code>, <code>c</code>, а затем <code>b</code>, стек выглядит так:</p><pre><code>| | |b| |c| |a| \_\_\_ </code></pre><p>и когда я нажимаю кнопку «назад», <code>b</code> уничтожается и <code>c</code> возобновляется.</p><p>но когда я запускаю фрагмент <code>a</code> во второй раз, вместо возобновления из заднего стека, он добавляется в начало заднего стека</p><pre><code>| | |a| |c| |a| \_\_\_ </code></pre><p>но я хочу возобновить <code>a</code> и уничтожить все фрагменты поверх него (если есть). на самом деле, мне просто нравится поведение стека по умолчанию. </p><p>как мне это сделать?</p><p>ожидается: (<code>a</code> должен быть возобновлен, а верхние фрагменты должны быть уничтожены)</p><pre><code>| | | | | | |a| \_\_\_ </code></pre><p><strong> правка: (предложено a - c) </strong></p><p>это мой пробный код:</p><pre><code>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); } </code></pre><p>проблема в том, что когда я запускаю <code>a</code>, а затем <code>b</code>, затем нажимаю «назад», <code>b</code> удаляется и <code>a</code> возобновляется. и нажатие «назад» во второй раз должно выйти из приложения. но оно показывает пустое окно, и я должен нажать в третий раз, чтобы закрыть его.</p><p>кроме того, когда я запускаю <code>a</code>, затем <code>b</code>, затем <code>c</code>, затем <code>b</code> снова ...</p><p>ожидаемое:</p><pre><code>| | | | |b| |a| \_\_\_ </code></pre><p>актуально:</p><pre><code>| | |b| |b| |a| \_\_\_ </code></pre><p>должен ли я переопределить <code>onbackpressed()</code> при любой настройке или я что-то упустил?</p>

<p>читая <a href="http://developer.android.com/reference/android/support/v4/app/fragmentmanager.html">документацию</a> , есть способ извлечь задний стек на основе либо имени транзакции, либо идентификатора, предоставленного коммитом. использовать имя <em>may</em> проще, так как не требуется отслеживать число, которое может измениться, и усиливается логика «уникального входа в стек назад».</p><p>поскольку для <code>fragment</code> требуется только одна запись обратного стека, присвойте имени обратного состояния имя класса фрагмента (через <code>getclass().getname()</code>). затем при замене <code>fragment</code> используйте метод <a href="http://developer.android.com/reference/android/support/v4/app/fragmentmanager.html#popbackstackimmediate%28java.lang.string,%20int%29"><code>popbackstackimmediate()</code></a> . если он возвращает true, это означает, что в заднем стеке есть экземпляр фрагмента. если нет, фактически выполните логику замены фрагмента.</p><pre><code>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(); } } </code></pre><hr><p><strong>правка</strong></p><blockquote> <p>проблема в том, что когда я запускаю a, а затем b, затем нажимаю кнопку возврата, b удаляется и а возобновляется. и снова нажать кнопку возврата выйдите из приложения. но он показывает пустое окно и нуждается в другом нажатии закрыть это.</p> </blockquote><p>это связано с тем, что <code>fragmenttransaction</code> добавляется в задний стек, чтобы гарантировать, что мы сможем позже вставить фрагменты сверху. быстрое решение этой проблемы - переопределение <code>onbackpressed()</code> и завершение activity, если задний стек содержит только 1 <code>fragment</code> </p><pre><code>@override public void onbackpressed(){ if (getsupportfragmentmanager().getbackstackentrycount() == 1){ finish(); } else { super.onbackpressed(); } } </code></pre><p>что касается дублирующих записей обратного стека, ваш условный оператор, который заменяет фрагмент, если он не был извлечен, явно <em>отличается</em> от того, что был в моем исходном фрагменте кода. то, что вы делаете, это добавление в задний стек независимо от того, был ли задний стек извлечен.</p><p>примерно так должно быть ближе к тому, что вы хотите:</p><pre><code>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(); } } </code></pre><p>условие немного изменилось, так как выбор одного и того же фрагмента, когда он был виден, также приводил к дублированию записей.</p><p>реализация:</p><p>я настоятельно рекомендую не разбирать обновленный метод <code>replacefragment()</code>, как вы это делали в своем коде. вся логика содержится в этом методе, и перемещение деталей может вызвать проблемы.</p><p>это означает, что вы должны скопировать обновленный метод <code>replacefragment()</code> в ваш класс, а затем изменить</p><pre><code>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(); </code></pre><p>так просто</p><pre><code>replacefragment (fragmentname); </code></pre><hr><p><strong>edit # 2</strong></p><p>чтобы обновить ящик при изменении заднего стека, создайте метод, который принимает фрагмент и сравнивает имена классов. если что-то совпадает, измените название и выбор. также добавьте <code>onbackstackchangedlistener</code> и вызовите метод обновления, если существует допустимый фрагмент.</p><p>например, в <code>oncreate()</code> действия добавьте</p><pre><code>getsupportfragmentmanager().addonbackstackchangedlistener(new onbackstackchangedlistener() { @override public void onbackstackchanged() { fragment f = getsupportfragmentmanager().findfragmentbyid(r.id.content\_frame); if (f != null){ updatetitleanddrawer (f); } } }); </code></pre><p>и другой метод:</p><pre><code>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 } } </code></pre><p>теперь, когда меняется задний стек, заголовок и проверенная позиция будут отображать видимую переменную <code>fragment</code>.</p>

<p>я думаю, что этот метод может решить вашу проблему: </p><pre><code>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 (); } } </code></pre><p>который был первоначально размещен в <a href="https://stackoverflow.com/a/41414499/2581314">этот вопрос</a> </p>

<p><strong> шаг 1: </strong>реализовать интерфейс с вашим классом активности</p><pre><code>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 } } </code></pre><p><strong> шаг 2: </strong>когда вы вызываете другой фрагмент, добавьте этот метод:</p><pre><code>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(); </code></pre>

<p>более простым решением будет изменение этой линии</p><p><code>ft.replace(r.id.content\_frame, a);</code> to <code>ft.add(r.id.content\_frame, a);</code></p><p>и внутри вашего xml-макета, пожалуйста, используйте </p><pre><code> android:background="@color/white" android:clickable="true" android:focusable="true" </code></pre><p><code>clickable</code> означает, что на него можно щелкнуть указательным устройством или коснуться сенсорного устройства.</p><p><code>focusable</code> означает, что он может получить фокус от устройства ввода, такого как клавиатура. устройства ввода, такие как клавиатуры, не могут решить, в какое представление отправлять свои события ввода, основываясь на самих вводах, поэтому они отправляют их в представление, которое имеет фокус.</p>

<blockquote> <p>я знаю, что уже поздно отвечать на этот вопрос, но я решил эту проблему сам и подумал, что стоит поделиться ею со всеми.</p> </blockquote><pre><code>public void replacefragment(basefragment fragment) { fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); final fragmentmanager fmanager = getsupportfragmentmanager(); basefragment fragm = (basefragment) fmanager.findfragmentbytag(fragment.getfragmenttag()); transaction.setcustomanimations(r.anim.enter\_from\_right, r.anim.exit\_to\_left, r.anim.enter\_from\_left, r.anim.exit\_to\_right); if (fragm == null) { //here fragment is not available in the stack transaction.replace(r.id.container, fragment, fragment.getfragmenttag()); transaction.addtobackstack(fragment.getfragmenttag()); } else { //fragment was found in the stack , now we can reuse the fragment // please do not add in back stack else it will add transaction in back stack transaction.replace(r.id.container, fragm, fragm.getfragmenttag()); } transaction.commit(); } </code></pre><blockquote> <p>и в onbackpressed ()</p> </blockquote><pre><code> @override public void onbackpressed() { if(getsupportfragmentmanager().getbackstackentrycount()>1){ super.onbackpressed(); }else{ finish(); } } </code></pre>

<pre><code>getfragmentmanager().addonbackstackchangedlistener(new fragmentmanager.onbackstackchangedlistener() { @override public void onbackstackchanged() { if(getfragmentmanager().getbackstackentrycount()==0) { onresume(); } } }); </code></pre>