Bagaimana cara melanjutkan Fragmen dari BackStack jika ada

151

Saya belajar cara menggunakan fragmen. Saya memiliki tiga contoh Fragmentyang diinisialisasi di bagian atas kelas. Saya menambahkan fragmen ke aktivitas seperti ini:

Mendeklarasikan dan menginisialisasi:

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

Mengganti / menambahkan:

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

Cuplikan ini berfungsi dengan baik. Setiap fragmen melekat pada aktivitas, dan disimpan ke tumpukan belakang tanpa masalah.

Jadi ketika saya meluncurkan A,, Cdan kemudian B, tumpukan terlihat seperti ini:

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

Dan ketika saya menekan tombol 'kembali', Bdihancurkan dan Cdilanjutkan.

Tetapi, ketika saya meluncurkan fragmen Auntuk kedua kalinya, alih-alih melanjutkan dari tumpukan belakang, itu ditambahkan di bagian atas tumpukan belakang

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

Tetapi saya ingin melanjutkan Adan menghancurkan semua fragmen di atasnya (jika ada). Sebenarnya, saya hanya suka perilaku back-stack standar.

Bagaimana saya mencapai ini?

Diharapkan: ( Aharus dilanjutkan dan fragmen atas harus dihancurkan)

| |
| |
| |
|A|
___

Edit: (disarankan oleh A - C)

Ini adalah kode percobaan saya:

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

Masalahnya adalah, ketika saya meluncurkan Adan kemudian B, kemudian tekan 'kembali', Bdihapus dan Adilanjutkan. dan menekan 'kembali' untuk kedua kalinya harus keluar dari aplikasi. Tapi itu menunjukkan jendela kosong dan saya harus menekan kembali untuk ketiga kalinya untuk menutupnya.

Juga, ketika saya meluncurkan A, lalu B, lalu C, sekali Blagi ...

Diharapkan:

| |
| |
|B|
|A|
___

Sebenarnya:

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

Haruskah saya mengganti onBackPressed()dengan kustomisasi atau saya kehilangan sesuatu?

Kaidul
sumber

Jawaban:

279

Membaca dokumentasi , ada cara untuk mem-pop back stack berdasarkan nama transaksi atau id yang diberikan oleh komit. Menggunakan nama mungkin lebih mudah karena tidak harus melacak nomor yang dapat mengubah dan memperkuat logika "entri tumpukan unik".

Karena Anda hanya menginginkan satu entri tumpukan belakang per Fragment, jadikan nama belakang sebagai nama kelas Fragment (via getClass().getName()). Kemudian saat mengganti a Fragment, gunakan popBackStackImmediate()metode. Jika itu mengembalikan true, itu berarti ada sebuah instance dari Fragmen di tumpukan belakang. Jika tidak, sebenarnya jalankan logika penggantian Fragmen.

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

Masalahnya adalah - ketika saya meluncurkan A dan kemudian B, lalu tekan tombol kembali, B dihapus dan A dilanjutkan. dan menekan kembali tombol kembali harus keluar dari aplikasi. Tapi itu menunjukkan jendela kosong dan perlu tekan lagi untuk menutupnya.

Ini karena FragmentTransactionsedang ditambahkan ke tumpukan belakang untuk memastikan bahwa kami dapat memunculkan fragmen di atas nanti. Perbaikan cepat untuk ini adalah onBackPressed()mengganti dan menyelesaikan Kegiatan jika tumpukan belakang hanya berisi 1Fragment

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

Mengenai entri tumpukan duplikat kembali, pernyataan kondisional Anda yang menggantikan fragmen jika belum muncul jelas berbeda dari apa yang cuplikan kode asli saya. Apa yang Anda lakukan adalah menambahkan ke tumpukan belakang terlepas dari apakah tumpukan kembali muncul atau tidak.

Sesuatu seperti ini harus lebih dekat dengan apa yang Anda inginkan:

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

Persyaratan diubah sedikit karena memilih fragmen yang sama sementara itu terlihat juga menyebabkan entri duplikat.

Penerapan:

Saya sangat menyarankan untuk tidak replaceFragment()memisahkan metode yang diperbarui seperti yang Anda lakukan dalam kode Anda. Semua logika terkandung dalam metode ini dan bagian bergerak di sekitar dapat menyebabkan masalah.

Ini berarti Anda harus menyalin replaceFragment()metode yang diperbarui ke kelas Anda kemudian berubah

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

begitu sederhana

replaceFragment (fragmentName);

EDIT # 2

Untuk memperbarui laci ketika tumpukan belakang berubah, buat metode yang menerima dalam Fragmen dan membandingkan nama kelas. Jika ada yang cocok, ubah judul dan pilihan. Juga tambahkan OnBackStackChangedListenerdan minta metode pembaruan Anda jika ada Fragmen yang valid.

Misalnya, di Aktivitas onCreate(), tambahkan

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

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

  }
});

Dan metode lainnya:

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

Sekarang, setiap kali tumpukan belakang berubah, judul dan posisi yang dicentang akan mencerminkan yang terlihat Fragment.

A - C
sumber
Terima kasih atas jawaban anda. :) Ini berfungsi sebagian. Saya telah mengedit pertanyaan saya dengan kemajuan. Silakan lihat)
Kaidul
2
@RishabhSrivastava perlu diingat "pop" biasanya menghancurkan fragmen paling atas. Adapun metode mana yang disebut pertama, itu tergantung - lihatlah Fragment Lifecycle. Untuk kasus Anda, saya akan menggunakan OnBackstackChangedListenersehingga Anda dapat menjamin bahwa Anda berurusan dengan Fragmen yang tepat.
A - C
2
@RishabhSrivastava Itulah sebabnya saya menyarankan OnBackstackChangedListenerjuga.
A - C
1
@AlexanderFarber Anda benar. Kembali ketika saya membuat jawaban ini saya tidak memikirkan instanceof:)
A - C
2
Solusi Anda terdengar bagus, tetapi jika Anda memiliki tiga fragmen dan klik pada mereka A-B-C-Bdan tekan kembali satu kali Anda akan kembali ke Adan tidak kembali ke C. Itu karena popBackStackImmediatetidak hanya muncul tag yang diberikan tetapi juga segala sesuatu di atas. Apakah ada pekerjaan di sana?
Raeglan
10

Saya pikir metode ini memecahkan masalah Anda:

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


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    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 ();
    }
}

yang semula diposting di Pertanyaan Ini

erluxman
sumber
Harap beri tahu saya jika menjawab seperti ini melanggar aturan yang saya inginkan agar mudah dilihat orang di pos ini. Terima kasih ..
erluxman
Saya tidak yakin apakah itu melanggar aturan (mungkin tidak), tapi ini pasti membantu
Kathir
1
Apa gunanya baris ketiga? -> manager.findFragmentByTag (tag); Sepertinya tidak melakukan apa-apa ..
Rik van Velzen
3

Langkah 1: Terapkan antarmuka dengan kelas aktivitas Anda

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

Langkah 2: Saat Anda memanggil fragmen lain, tambahkan metode ini:

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();
Vinod Joshi
sumber
2

Saya tahu ini sudah cukup terlambat untuk menjawab pertanyaan ini tetapi saya menyelesaikan masalah ini sendiri dan berpikir layak untuk dibagikan kepada semua orang.`

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

Dan di onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Vivek Pratap Singh
sumber
1
@ X-Black ... BaseFragment adalah kelas dasar untuk setiap fragmen yang digunakan dalam aplikasi.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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

Solusi yang lebih mudah akan mengubah baris ini

ft.replace(R.id.content_frame, A); untuk ft.add(R.id.content_frame, A);

Dan di dalam tata letak XML Anda, silakan gunakan

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable artinya dapat diklik oleh perangkat penunjuk atau disadap oleh perangkat sentuh.

Focusableberarti dapat memperoleh fokus dari perangkat input seperti keyboard. Perangkat input seperti keyboard tidak dapat memutuskan tampilan mana yang akan dikirimi aktivitas input berdasarkan input itu sendiri, sehingga mereka mengirimnya ke tampilan yang memiliki fokus.

Hossam Hassan
sumber