Pisahkan Stack Kembali untuk setiap tab di Android menggunakan Fragmen

158

Saya mencoba menerapkan tab untuk navigasi di aplikasi Android. Karena TabActivity dan ActivityGroup sudah tidak digunakan lagi, saya ingin mengimplementasikannya menggunakan Fragmen.

Saya tahu cara mengatur satu fragmen untuk setiap tab dan kemudian beralih fragmen ketika tab diklik. Tetapi bagaimana saya bisa memiliki tumpukan belakang terpisah untuk setiap tab?

Sebagai contoh, Fragmen A dan B akan berada di bawah Tab 1 dan Fragmen C dan D di bawah Tab 2. Ketika aplikasi dimulai, Fragmen A ditampilkan dan Tab 1 dipilih. Kemudian Fragmen A mungkin diganti dengan Fragmen B. Ketika Tab 2 dipilih, Fragmen C harus ditampilkan. Jika Tab 1 kemudian dipilih Fragmen B harus sekali lagi ditampilkan. Pada titik ini, dimungkinkan untuk menggunakan tombol kembali untuk menampilkan Fragmen A.

Juga, penting bahwa keadaan untuk setiap tab dipertahankan ketika perangkat diputar.

BR Martin

mardah
sumber

Jawaban:

23

Kerangka kerja saat ini tidak akan melakukan ini untuk Anda secara otomatis. Anda perlu membuat dan mengelola tumpukan belakang Anda sendiri untuk setiap tab.

Sejujurnya, ini sepertinya hal yang sangat dipertanyakan untuk dilakukan. Saya tidak dapat membayangkannya menghasilkan UI yang layak - jika tombol kembali akan melakukan hal-hal yang berbeda tergantung pada tab saya, terutama jika tombol kembali juga memiliki perilaku normal untuk menutup seluruh aktivitas ketika di bagian atas tumpukan ... kedengarannya buruk.

Jika Anda mencoba untuk membangun sesuatu seperti UI browser web, untuk mendapatkan UX yang alami bagi pengguna akan melibatkan banyak perubahan perilaku halus tergantung pada konteksnya, jadi Anda pasti perlu melakukan back stack Anda sendiri manajemen daripada mengandalkan beberapa implementasi standar dalam kerangka kerja. Sebagai contoh coba perhatikan bagaimana tombol kembali berinteraksi dengan browser standar dalam berbagai cara Anda dapat masuk dan keluar dari itu. (Setiap "jendela" di browser pada dasarnya adalah sebuah tab.)

hackbod
sumber
7
Jangan lakukan itu. Dan kerangka itu hampir tidak berguna. Itu tidak memberi Anda dukungan otomatis untuk hal semacam ini, yang seperti yang saya katakan saya tidak bisa bayangkan menghasilkan pengalaman pengguna yang baik kecuali dalam situasi yang sangat khusus di mana Anda akan perlu untuk berhati-hati mengendalikan perilaku kembali.
hackbod
9
Jenis navigasi ini, maka Anda memiliki tab dan hierarki halaman pada setiap tab sangat umum untuk aplikasi iPhone misalnya (Anda dapat memeriksa App Store dan aplikasi iPod). Saya menemukan pengalaman pengguna mereka cukup baik.
Dmitry Ryadnenko
13
Ini gila. IPhone bahkan tidak memiliki tombol kembali. Ada demo API yang menunjukkan kode yang sangat sederhana untuk mengimplementasikan fragmen di tab. Pertanyaan yang diajukan adalah tentang memiliki tumpukan belakang yang berbeda untuk setiap tab, dan tanggapan saya adalah bahwa kerangka kerja tidak menyediakan ini secara otomatis karena secara semantik apa yang dilakukan tombol kembali kemungkinan besar akan menjadi pengalaman pengguna yang buruk. Anda dapat dengan mudah menerapkan kembali semantik sendiri jika Anda mau.
hackbod
4
Sekali lagi, iPhone tidak memiliki tombol kembali, sehingga secara semantik tidak memiliki perilaku tumpukan seperti Android. Juga "lebih baik untuk hanya tongkat dengan kegiatan dan menyimpan sendiri banyak waktu" tidak masuk setiap akal di sini, karena kegiatan jangan biarkan Anda menempatkan menjaga tab di UI dengan kembali tumpukan yang berbeda mereka sendiri; sebenarnya pengelolaan back-stack kegiatan kurang fleksibel daripada apa yang disediakan oleh kerangka kerja Fragment.
hackbod
22
@hackbod Saya mencoba mengikuti poin Anda, tetapi mengalami kesulitan menerapkan perilaku back-stack kustom. Saya menyadari bahwa setelah terlibat dalam desain ini, Anda akan memiliki wawasan yang kuat tentang betapa mudahnya itu. Apakah mungkin ada aplikasi demo untuk use case OP, karena ini adalah situasi yang sangat umum, terutama bagi kita yang harus menulis & port aplikasi iOS untuk klien yang membuat permintaan ini .... manajemen terpisah backstacks fragmen dalam setiap FragmentActivity.
Richard Le Mesurier
138

Saya sangat terlambat untuk pertanyaan ini. Tetapi karena utas ini sangat informatif dan membantu saya, saya pikir lebih baik saya mengirim dua pence saya di sini.

Saya membutuhkan aliran layar seperti ini (Desain minimalis dengan 2 tab dan 2 tampilan di setiap tab),

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2

Saya memiliki persyaratan yang sama di masa lalu, dan saya melakukannya menggunakan TabActivityGroup(yang sudah usang pada saat itu juga) dan Kegiatan. Kali ini saya ingin menggunakan Fragmen.

Jadi ini adalah bagaimana saya melakukannya.

1. Buat Kelas Fragmen dasar

public class BaseFragment extends Fragment {
    AppMainTabActivity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = (AppMainTabActivity) this.getActivity();
    }

    public void onBackPressed(){
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data){
    }
}

Semua fragmen di aplikasi Anda dapat memperluas kelas Basis ini. Jika Anda ingin menggunakan fragmen khusus seperti ListFragmentAnda harus membuat kelas dasar untuk itu juga. Anda akan jelas tentang penggunaan onBackPressed()dan onActivityResult()jika Anda membaca posting secara penuh ..

2. Buat beberapa pengidentifikasi Tab, dapat diakses di mana saja dalam proyek

public class AppConstants{
    public static final String TAB_A  = "tab_a_identifier";
    public static final String TAB_B  = "tab_b_identifier";

    //Your other constants, if you have them..
}

tidak ada yang bisa dijelaskan di sini ..

3. Oke, Aktivitas Tab Utama - Silakan lihat komentar dalam kode ..

public class AppMainFragmentActivity extends FragmentActivity{
    /* Your Tab host */
    private TabHost mTabHost;

    /* A HashMap of stacks, where we use tab identifier as keys..*/
    private HashMap<String, Stack<Fragment>> mStacks;

    /*Save current tabs identifier in this..*/
    private String mCurrentTab;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_main_tab_fragment_layout);

        /*  
         *  Navigation stacks for each tab gets created.. 
         *  tab identifier is used as key to get respective stack for each tab
         */
        mStacks             =   new HashMap<String, Stack<Fragment>>();
        mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
        mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

        mTabHost                =   (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(listener);
        mTabHost.setup();

        initializeTabs();
    }


    private View createTabView(final int id) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView =   (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        return view;
    }

    public void initializeTabs(){
        /* Setup your tab icons and content views.. Nothing special in this..*/
        TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
        mTabHost.setCurrentTab(-3);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
        mTabHost.addTab(spec);


        spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
        mTabHost.addTab(spec);
    }


    /*Comes here when user switch tab, or we do programmatically*/
    TabHost.OnTabChangeListener listener    =   new TabHost.OnTabChangeListener() {
      public void onTabChanged(String tabId) {
        /*Set current tab..*/
        mCurrentTab                     =   tabId;

        if(mStacks.get(tabId).size() == 0){
          /*
           *    First time this tab is selected. So add first fragment of that tab.
           *    Dont need animation, so that argument is false.
           *    We are adding a new fragment which is not present in stack. So add to stack is true.
           */
          if(tabId.equals(AppConstants.TAB_A)){
            pushFragments(tabId, new AppTabAFirstFragment(), false,true);
          }else if(tabId.equals(AppConstants.TAB_B)){
            pushFragments(tabId, new AppTabBFirstFragment(), false,true);
          }
        }else {
          /*
           *    We are switching tabs, and target tab is already has atleast one fragment. 
           *    No need of animation, no need of stack pushing. Just show the target fragment
           */
          pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
        }
      }
    };


    /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
    public void setCurrentTab(int val){
          mTabHost.setCurrentTab(val);
    }


    /* 
     *      To add fragment to a tab. 
     *  tag             ->  Tab identifier
     *  fragment        ->  Fragment to show, in tab identified by tag
     *  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
     *                      true when when we are pushing more fragment into navigation stack. 
     *  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
     *                      true in all other cases.
     */
    public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
      if(shouldAdd)
          mStacks.get(tag).push(fragment);
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      if(shouldAnimate)
          ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }


    public void popFragments(){
      /*    
       *    Select the second last fragment in current tab's stack.. 
       *    which will be shown after the fragment transaction given below 
       */
      Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

      /*pop current fragment from stack.. */
      mStacks.get(mCurrentTab).pop();

      /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }   


    @Override
    public void onBackPressed() {
        if(mStacks.get(mCurrentTab).size() == 1){
          // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
          finish();
          return;
        }

        /*  Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
         *  when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
         *  kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
         */
        ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

        /* Goto previous fragment in navigation stack of this tab */
            popFragments();
    }


    /*
     *   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
     *  in that fragment, and called it from the activity. But couldn't resist myself.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(mStacks.get(mCurrentTab).size() == 0){
            return;
        }

        /*Now current fragment on screen gets onActivityResult callback..*/
        mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
    }
}

4. app_main_tab_fragment_layout.xml (Jika ada yang tertarik.)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

    </LinearLayout>
</TabHost>

5. AppTabAFirstFragment.java (Fragmen pertama di Tab A, serupa untuk semua Tab)

public class AppTabAFragment extends BaseFragment {
    private Button mGotoButton;

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

        mGoToButton =   (Button) view.findViewById(R.id.goto_button);
        mGoToButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
        }
    }
}

Ini mungkin bukan cara yang paling halus dan benar. Tapi itu berhasil dengan baik dalam kasus saya. Juga saya hanya memiliki persyaratan ini dalam mode potret. Saya tidak pernah harus menggunakan kode ini dalam proyek yang mendukung kedua orientasi. Jadi tidak bisa mengatakan tantangan apa yang saya hadapi di sana ..

EDIT:

Jika ada yang menginginkan proyek penuh, saya telah mendorong proyek sampel ke github .

Krishnabhadra
sumber
2
Menyimpan data untuk setiap fragmen, membuat ulang setiap fragmen, membangun kembali tumpukan ... begitu banyak pekerjaan untuk perubahan orientasi yang sederhana.
Michael Eilers Smith
3
@omegatai sepenuhnya setuju dengan Anda .. Semua masalah muncul karena Android tidak mengelola tumpukan untuk kami ( yang dilakukan iOS dan perubahan orientasi atau tab dengan beberapa fragmen sangat mudah ) dan yang akan membawa kami kembali ke diskusi awal dalam Q / Sebuah utas. Tidak ada gunanya kembali ke hal itu sekarang ..
Krishnabhadra
1
@Renjith Ini karena fragmen dibuat ulang setiap kali, ketika Anda beralih tab .. Jangan berpikir sekali pun, fragmen Anda digunakan kembali di seluruh tab switch. ketika saya beralih dari tab A ke B, tab A dibebaskan dari memori. Jadi simpan data Anda dalam aktivitas, dan setiap kali periksa apakah aktivitas memiliki data sebelum mencoba mendapatkannya dari server.
Krishnabhadra
2
@ Krishnabhadra Ok, itu terdengar jauh lebih baik. Biarkan saya memperbaiki kalau-kalau saya salah. Sebagai contoh Anda, hanya ada satu aktivitas dan oleh karena itu satu bundel. Buat instance adaptor di BaseFragment (merujuk proyek Anda) dan simpan data di sana. Gunakan mereka kapan pun tampilan akan dibangun.
Renjith
1
Berhasil. Terima kasih banyak. Mengunggah seluruh proyek adalah ide bagus! :-)
Vinay W
96

Kami harus menerapkan perilaku yang persis sama dengan yang Anda gambarkan untuk aplikasi baru-baru ini. Layar dan aliran keseluruhan aplikasi sudah ditentukan sehingga kami harus tetap menggunakannya (ini adalah tiruan aplikasi iOS ...). Untungnya, kami berhasil menyingkirkan tombol kembali di layar :)

Kami meretas solusi menggunakan campuran TabActivity, FragmentActivities (kami menggunakan perpustakaan dukungan untuk fragmen) dan Fragmen. Dalam retrospektif, saya cukup yakin itu bukan keputusan arsitektur terbaik, tetapi kami berhasil membuat hal itu berfungsi. Jika saya harus melakukannya lagi, saya mungkin akan mencoba untuk melakukan solusi berbasis aktivitas yang lebih (tidak ada fragmen), atau mencoba dan hanya memiliki satu Kegiatan untuk tab dan membiarkan semua yang lain dilihat (yang saya temukan jauh lebih banyak dapat digunakan kembali daripada aktivitas keseluruhan).

Jadi persyaratannya adalah memiliki beberapa tab dan layar yang bisa di-nestable di setiap tab:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6

dll ...

Jadi katakan: pengguna mulai pada tab 1, menavigasi dari layar 1 ke layar 2 lalu ke layar 3, ia kemudian beralih ke tab 3 dan menavigasi dari layar 4 hingga 6; jika beralih kembali ke tab 1, ia harus melihat layar 3 lagi dan jika ia menekan Kembali ia harus kembali ke layar 2; Kembali lagi dan dia ada di layar 1; beralih ke tab 3 dan dia ada di layar 6 lagi.

Aktivitas utama dalam aplikasi adalah MainTabActivity, yang memperluas TabActivity. Setiap tab dikaitkan dengan suatu aktivitas, katakanlah ActivityInTab1, 2 dan 3. Dan kemudian setiap layar akan menjadi sebuah fragmen:

MainTabActivity
  ActivityInTab1
    Fragment1 -> Fragment2 -> Fragment3
  ActivityInTab2
    Fragment4
  ActivityInTab3
    Fragment5 -> Fragment6

Setiap ActivityInTab hanya memegang satu fragmen pada satu waktu, dan tahu bagaimana cara mengganti satu fragmen dengan yang lain (hampir sama dengan ActvityGroup). Yang keren adalah cukup mudah untuk menjaga tumpukan back terpisah untuk setiap tab dengan cara ini.

Fungsionalitas untuk setiap ActivityInTab cukup sama: tahu cara menavigasi dari satu fragmen ke fragmen lain dan mempertahankan tumpukan belakang, jadi kami menempatkannya di kelas dasar. Sebut saja ActivityInTab:

abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_tab);
    }

    /**
     * Navigates to a new fragment, which is added in the fragment container
     * view.
     * 
     * @param newFragment
     */
    protected void navigateTo(Fragment newFragment) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();

        ft.replace(R.id.content, newFragment);

        // Add this transaction to the back stack, so when the user presses back,
        // it rollbacks.
        ft.addToBackStack(null);
        ft.commit();
    }

}

Activity_in_tab.xml hanya ini:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:isScrollContainer="true">
</RelativeLayout>

Seperti yang Anda lihat, tata letak tampilan untuk setiap tab adalah sama. Itu karena itu hanya konten yang disebut FrameLayout yang akan menampung setiap fragmen. Fragmen adalah yang memiliki tampilan layar masing-masing.

Hanya untuk poin bonus, kami juga menambahkan beberapa kode kecil untuk menampilkan dialog konfirmasi ketika pengguna menekan Kembali dan tidak ada lagi fragmen untuk kembali ke:

// In ActivityInTab.java...
@Override
public void onBackPressed() {
    FragmentManager manager = getSupportFragmentManager();
    if (manager.getBackStackEntryCount() > 0) {
        // If there are back-stack entries, leave the FragmentActivity
        // implementation take care of them.
        super.onBackPressed();
    } else {
        // Otherwise, ask user if he wants to leave :)
        showExitDialog();
    }
}

Cukup banyak pengaturannya. Seperti yang Anda lihat, masing-masing FragmentActivity (atau hanya aktivitas di Android> 3) mengurus semua penumpukan kembali dengan FragmentManager itu sendiri.

Aktivitas seperti ActivityInTab1 akan sangat sederhana, itu hanya akan menunjukkan fragmen pertama (yaitu layar):

public class ActivityInTab1 extends ActivityInTab {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navigateTo(new Fragment1());
    }
}

Kemudian, jika sebuah fragmen perlu bernavigasi ke fragmen lain, ia harus melakukan casting yang tidak menyenangkan ... tapi itu tidak terlalu buruk:

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());

Jadi cukup banyak. Saya cukup yakin ini bukan solusi yang sangat kanonik (dan sebagian besar tentu tidak sangat baik), jadi saya ingin bertanya kepada pengembang Android berpengalaman apa yang akan menjadi pendekatan yang lebih baik untuk mencapai fungsi ini, dan jika ini bukan "bagaimana ini" dilakukan" di Android, saya akan menghargai jika Anda bisa mengarahkan saya ke beberapa link atau materi yang menjelaskan yang satu cara Android untuk pendekatan ini (tab, layar bersarang di tab, dll). Silakan merobek jawaban ini di komentar :)

Sebagai tanda bahwa solusi ini tidak terlalu baik adalah bahwa baru-baru ini saya harus menambahkan beberapa fungsi navigasi ke aplikasi. Beberapa tombol aneh yang harus membawa pengguna dari satu tab ke tab lain dan ke layar bersarang. Melakukan hal itu secara terprogram adalah rasa sakit di pantat, karena siapa-tahu-siapa masalah dan berurusan dengan kapan fragmen dan kegiatan sebenarnya dipakai dan diinisialisasi. Saya pikir itu akan jauh lebih mudah jika layar dan tab itu semua benar-benar hanya Tampilan.


Terakhir, jika Anda perlu selamat dari perubahan orientasi, penting bahwa fragmen Anda dibuat menggunakan setArguments / getArguments. Jika Anda menetapkan variabel contoh di konstruktor fragmen Anda, Anda akan kacau. Tapi untungnya itu sangat mudah diperbaiki: cukup simpan semuanya di setArguments di konstruktor dan kemudian ambil hal-hal itu dengan getArguments di onCreate untuk menggunakannya.

epidemi
sumber
13
Jawaban yang bagus tetapi saya pikir sangat sedikit yang akan melihat ini. Saya memilih jalan yang sama persis (seperti yang Anda lihat dari percakapan di jawaban sebelumnya) dan saya tidak senang dengan itu sama seperti Anda. Saya pikir Google benar-benar mengacaukan fragmen ini karena API ini tidak mencakup kasus penggunaan utama. Masalah lain yang mungkin Anda temui adalah ketidakmungkinan untuk menanamkan fragmen ke dalam fragmen lain.
Dmitry Ryadnenko
Terima kasih atas boulder komentar. Ya, saya sangat setuju dengan API fragmen. Saya sudah mengalami masalah fragmen bersarang (itu sebabnya kami pergi untuk pendekatan "ganti satu fragmen dengan yang lain" hehe).
epidemi
1
Saya telah menerapkan ini melalui SEMUA aktivitas. Saya tidak suka apa yang saya dapatkan dan saya akan mencoba Fragmen. Itu kebalikan dari pengalaman Anda! Ada banyak implementasi dengan Kegiatan untuk menangani siklus pandangan anak di setiap tab dan juga untuk menerapkan tombol kembali Anda sendiri. Juga, Anda tidak bisa hanya menyimpan referensi ke semua tampilan atau Anda akan meledakkan memori. Saya harap Fragmen akan: 1) Mendukung siklus hidup Fragmen dengan pemisahan memori yang jelas dan 2) membantu menerapkan fungsi tombol kembali Plus, jika Anda menggunakan fragmen untuk proses ini, bukankah lebih mudah untuk dijalankan di Tablet?
gregm
Apa yang terjadi ketika pengguna beralih tab? Apakah backstack Fragment bisa dihapus? Bagaimana memastikan backstack tetap?
gregm
1
@gregm Jika Anda masuk 1 tab <-> 1 aktivitas seperti yang saya lakukan, backstack untuk setiap tab akan tetap ketika tab diaktifkan karena kegiatan itu benar-benar tetap hidup; mereka hanya dijeda dan dilanjutkan. Saya tidak tahu apakah ada cara untuk membuat kegiatan dihancurkan dan dibuat kembali ketika tab diaktifkan di TabActivity. Namun, jika Anda membuat fragmen dalam kegiatan diganti seperti saya menyarankan, mereka yang hancur (dan re-dibuat ketika backstack yang muncul). Jadi, Anda akan memiliki paling banyak satu fragmen hidup per tab kapan saja.
epidemi
6

Menyimpan referensi kuat untuk fragmen bukan cara yang benar.

FragmentManager menyediakan putFragment(Bundle, String, Fragment)dan saveFragmentInstanceState(Fragment).

Salah satu sudah cukup untuk menerapkan backstack.


Dengan menggunakan putFragment, alih-alih mengganti Fragmen, Anda melepaskan yang lama dan menambahkan yang baru. Inilah yang dilakukan kerangka kerja untuk mengganti transaksi yang ditambahkan ke backstack. putFragmentmenyimpan indeks ke daftar Fragmen aktif saat ini dan Fragmen tersebut disimpan oleh kerangka kerja selama perubahan orientasi.

Cara kedua, menggunakan saveFragmentInstanceState, menyimpan seluruh status fragmen ke Bundel memungkinkan Anda untuk benar-benar menghapusnya, daripada melepaskannya. Menggunakan pendekatan ini membuat tumpukan belakang lebih mudah untuk dimanipulasi, karena Anda dapat memunculkan Fragmen kapan pun Anda mau.


Saya menggunakan metode kedua untuk usecase ini:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/

Saya tidak ingin pengguna kembali ke layar Daftar, dari yang ketiga, dengan menekan tombol kembali. Saya juga melakukan animasi flip di antara mereka (menggunakan onCreateAnimation), sehingga solusi hacky tidak akan berfungsi, minimal tanpa pengguna jelas melihat ada sesuatu yang tidak benar.

Ini adalah kasus penggunaan yang valid untuk backstack khusus, melakukan apa yang diharapkan pengguna ...

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";

private MyBackStack mBackStack;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);

    if (state == null) {
        mBackStack = new MyBackStack();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.add(R.id.act_base_frg_container, new SignInFragment());
        tr.commit();
    } else {
        mBackStack = state.getParcelable(STATE_BACKSTACK);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(STATE_BACKSTACK, mBackStack);
}

private void showFragment(Fragment frg, boolean addOldToBackStack) {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);

    FragmentTransaction tr = fm.beginTransaction();
    tr.replace(R.id.act_base_frg_container, frg);
    // This is async, the fragment will only be removed after this returns
    tr.commit();

    if (addOldToBackStack) {
        mBackStack.push(fm, oldFrg);
    }
}

@Override
public void onBackPressed() {
    MyBackStackEntry entry;
    if ((entry = mBackStack.pop()) != null) {
        Fragment frg = entry.recreate(this);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.replace(R.id.act_base_frg_container, frg);
        tr.commit();

        // Pop it now, like the framework implementation.
        fm.executePendingTransactions();
    } else {
        super.onBackPressed();
    }
}

public class MyBackStack implements Parcelable {

    private final List<MyBackStackEntry> mList;

    public MyBackStack() {
        mList = new ArrayList<MyBackStackEntry>(4);
    }

    public void push(FragmentManager fm, Fragment frg) {
        push(MyBackStackEntry.newEntry(fm, frg);
    }

    public void push(MyBackStackEntry entry) {
        if (entry == null) {
            throw new NullPointerException();
        }
        mList.add(entry);
    }

    public MyBackStackEntry pop() {
        int idx = mList.size() - 1;
        return (idx != -1) ? mList.remove(idx) : null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        final int len = mList.size();
        dest.writeInt(len);
        for (int i = 0; i < len; i++) {
            // MyBackStackEntry's class is final, theres no
            // need to use writeParcelable
            mList.get(i).writeToParcel(dest, flags);
        }
    }

    protected MyBackStack(Parcel in) {
        int len = in.readInt();
        List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
        for (int i = 0; i < len; i++) {
            list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
        }
        mList = list;
    }

    public static final Parcelable.Creator<MyBackStack> CREATOR =
        new Parcelable.Creator<MyBackStack>() {

            @Override
            public MyBackStack createFromParcel(Parcel in) {
                return new MyBackStack(in);
            }

            @Override
            public MyBackStack[] newArray(int size) {
                return new MyBackStack[size];
            }
    };
}

public final class MyBackStackEntry implements Parcelable {

    public final String fname;
    public final Fragment.SavedState state;
    public final Bundle arguments;

    public MyBackStackEntry(String clazz, 
            Fragment.SavedState state,
            Bundle args) {
        this.fname = clazz;
        this.state = state;
        this.arguments = args;
    }

    public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
        final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
        final String name = frg.getClass().getName();
        final Bundle args = frg.getArguments();
        return new MyBackStackEntry(name, state, args);
    }

    public Fragment recreate(Context ctx) {
        Fragment frg = Fragment.instantiate(ctx, fname);
        frg.setInitialSavedState(state);
        frg.setArguments(arguments);
        return frg;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(fname);
        dest.writeBundle(arguments);

        if (state == null) {
            dest.writeInt(-1);
        } else if (state.getClass() == Fragment.SavedState.class) {
            dest.writeInt(0);
            state.writeToParcel(dest, flags);
        } else {
            dest.writeInt(1);
            dest.writeParcelable(state, flags);
        }
    }

    protected MyBackStackEntry(Parcel in) {
        final ClassLoader loader = getClass().getClassLoader();
        fname = in.readString();
        arguments = in.readBundle(loader);

        switch (in.readInt()) {
            case -1:
                state = null;
                break;
            case 0:
                state = Fragment.SavedState.CREATOR.createFromParcel(in);
                break;
            case 1:
                state = in.readParcelable(loader);
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
        new Parcelable.Creator<MyBackStackEntry>() {

            @Override
            public MyBackStackEntry createFromParcel(Parcel in) {
                return new MyBackStackEntry(in);
            }

            @Override
            public MyBackStackEntry[] newArray(int size) {
                return new MyBackStackEntry[size];
            }
    };
}
sergio91pt
sumber
2

Penolakan:


Saya merasa ini adalah tempat terbaik untuk mengirim solusi terkait yang telah saya kerjakan untuk jenis masalah serupa yang tampaknya merupakan barang standar Android. Ini tidak akan menyelesaikan masalah untuk semua orang, tetapi mungkin membantu beberapa orang.


Jika perbedaan utama antara fragmen Anda hanya data yang mencadangkannya (yaitu, tidak banyak perbedaan tata letak yang besar), maka Anda mungkin tidak perlu benar-benar mengganti fragmen, tetapi hanya menukar data yang mendasarinya dan menyegarkan tampilan.

Berikut ini deskripsi dari satu contoh yang mungkin untuk pendekatan ini:

Saya memiliki aplikasi yang menggunakan ListViews. Setiap item dalam daftar adalah orang tua dengan sejumlah anak. Saat Anda mengetuk item, daftar baru harus dibuka dengan anak-anak itu, dalam tab ActionBar yang sama dengan daftar aslinya. Daftar bersarang ini memiliki tata letak yang sangat mirip (beberapa penyesuaian bersyarat di sana-sini mungkin), tetapi datanya berbeda.

Aplikasi ini memiliki beberapa lapisan keturunan di bawah daftar induk awal dan kami mungkin atau mungkin tidak memiliki data dari server pada saat pengguna mencoba mengakses kedalaman tertentu di luar yang pertama. Karena daftar dibuat dari kursor basis data, dan fragmen menggunakan loader kursor dan adaptor kursor untuk mengisi tampilan daftar dengan item daftar, semua yang perlu terjadi ketika klik terdaftar adalah:

1) Buat adaptor baru dengan bidang 'ke' dan 'dari' yang sesuai yang akan cocok dengan tampilan item baru yang ditambahkan ke daftar dan kolom dikembalikan oleh kursor baru.

2) Tetapkan adaptor ini sebagai adaptor baru untuk ListView.

3) Bangun URI baru berdasarkan item yang diklik dan mulai ulang pemuat kursor dengan URI baru (dan proyeksi). Dalam contoh ini, URI dipetakan ke permintaan tertentu dengan argumen pilihan diturunkan dari UI.

4) Ketika data baru telah dimuat dari URI, tukar kursor yang terkait dengan adaptor ke kursor baru, dan daftar kemudian akan di-refresh.

Tidak ada backstack yang terkait dengan ini karena kami tidak menggunakan transaksi, jadi Anda harus membuat sendiri, atau memutar kueri secara terbalik saat mundur dari hierarki. Ketika saya mencoba ini, pertanyaannya cukup cepat sehingga saya hanya menjalankannya lagi di oNBackPressed () hingga saya berada di puncak hierarki, pada titik mana kerangka kerja mengambil alih tombol kembali lagi.

Jika Anda menemukan diri Anda dalam situasi yang sama, pastikan untuk membaca dokumen: http://developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

Saya harap ini membantu seseorang!

pelataran
sumber
Jika ada yang melakukan ini, dan juga menggunakan SectionIndexer (seperti AlphabetIndexer), Anda mungkin memperhatikan bahwa setelah mengganti adaptor, pengguliran cepat Anda tidak berfungsi. Jenis bug yang tidak menguntungkan, tetapi mengganti adaptor, bahkan dengan pengindeks baru, tidak memperbarui daftar bagian yang digunakan oleh FastScroll. Ada solusinya, silakan lihat: deskripsi masalah dan solusinya
courtf
2

Saya memiliki masalah yang persis sama dan mengimplementasikan proyek github open source yang mencakup tab yang ditumpuk, navigasi kembali ke atas dan diuji dan didokumentasikan dengan baik:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Ini adalah kerangka kerja sederhana dan kecil untuk tab navigasi dan pengalihan fragmen dan penanganan navigasi atas dan belakang. Setiap tab memiliki tumpukan fragmen sendiri. Ini menggunakan ActionBarSherlock dan kompatibel kembali ke API level 8.

Sebastian Baltes
sumber
2

Ini adalah masalah yang kompleks karena Android hanya menangani 1 back stack, tetapi ini layak. Butuh waktu berhari-hari untuk membuat perpustakaan bernama Tab Stacker yang melakukan persis seperti yang Anda cari: riwayat fragmen untuk setiap tab. Ini adalah open source dan sepenuhnya didokumentasikan, dan dapat dimasukkan dengan mudah dengan gradle. Anda dapat menemukan perpustakaan di github: https://github.com/smart-fun/TabStacker

Anda juga dapat mengunduh aplikasi sampel untuk melihat bahwa perilaku tersebut sesuai dengan kebutuhan Anda:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

Jika Anda memiliki pertanyaan jangan ragu untuk mengirim email.

Arnaud SmartFun
sumber
2

Saya ingin menyarankan solusi saya sendiri jika seseorang mencari dan ingin mencoba dan memilih yang terbaik untuk kebutuhannya.

https://github.com/drusak/tabactivity

Tujuan membuat perpustakaan cukup dangkal - mengimplementasikannya seperti iPhone.

Keuntungan utama:

  • gunakan pustaka android.support.design dengan TabLayout;
  • setiap tab memiliki tumpukan sendiri menggunakan FragmentManager (tanpa menyimpan referensi fragmen);
  • dukungan untuk tautan dalam (ketika Anda perlu membuka tab tertentu dan level fragmen tertentu di dalamnya);
  • menyimpan / mengembalikan status tab;
  • metode siklus fragmen adaptif dalam tab;
  • cukup mudah diimplementasikan untuk kebutuhan Anda.
kasurd
sumber
Terima kasih, ini sangat membantu. Saya perlu menggunakan ListFragments selain Fragments jadi saya menggandakan BaseTabFragment.java ke BaseTabListFragment.java dan telah memperpanjang ListFragment. Kemudian saya harus mengubah berbagai bagian dalam kode di mana ia selalu dianggap mengharapkan BaseTabFragment. Apakah ada cara yang lebih baik?
primehalo
Sayangnya, Tidak memikirkan ListFragment. Secara teknis ini adalah solusi yang tepat, tetapi akan membutuhkan pemeriksaan tambahan untuk TabFragment dan instanceOf BaseTabListFragment. Pendekatan lain untuk menggunakan Fragment dengan ListView di dalam (persis sama dengan ListFragment diimplemetasikan). Saya akan memikirkannya. Terima kasih telah mengarahkan saya pada hal itu!
kasurd
1

Solusi sederhana:

Setiap kali Anda mengubah panggilan tampilan tab / root:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Ini akan menghapus BackStack. Ingat untuk memanggil ini sebelum Anda mengubah fragmen root.

Dan tambahkan fragmen dengan ini:

FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

Perhatikan .addToBackStack(null)dan transaction.addmisalnya dapat diubah dengan transaction.replace.

Morten Holmgaard
sumber
-1

Utas ini sangat sangat menarik dan bermanfaat.
Terima kasih Krishnabhadra untuk penjelasan dan kode Anda, saya menggunakan kode Anda dan sedikit ditingkatkan, memungkinkan untuk bertahan tumpukan, currentTab, dll ... dari perubahan konfigurasi (berputar terutama).
Diuji pada perangkat nyata 4.0.4 dan 2.3.6, tidak diuji pada emulator

Saya mengubah bagian kode ini pada "AppMainTabActivity.java", sisanya tetap sama. Mungkin Krishnabhadra akan menambahkan ini pada kodenya.

Pulihkan data onCreate:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.app_main_tab_fragment_layout);

    /*  
     *  Navigation stacks for each tab gets created..
     *  tab identifier is used as key to get respective stack for each tab
     */

  //if we are recreating this activity...
    if (savedInstanceState!=null) {
         mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack");
         mCurrentTab = savedInstanceState.getString("currentTab");
    }
    else {
    mStacks = new HashMap<String, Stack<Fragment>>();
    mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
    mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

    }

    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    initializeTabs();

  //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab
    mTabHost.setOnTabChangedListener(listener);
}

Simpan variabel dan masukkan ke Bundel:

 //Save variables while recreating
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("stack", mStacks);
    outState.putString("currentTab", mCurrentTab);
    //outState.putInt("tabHost",mTabHost);
}

Jika ada CurrentTab sebelumnya, atur ini, buat Tab_A baru:

public void initializeTabs(){
    /* Setup your tab icons and content views.. Nothing special in this..*/
    TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);

    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_a_state_btn));
    mTabHost.addTab(spec);


    spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_b_state_btn));
    mTabHost.addTab(spec);

//if we have non default Tab as current, change it
    if (mCurrentTab!=null) {
        mTabHost.setCurrentTabByTag(mCurrentTab);
    } else {
        mCurrentTab=AppConstants.TAB_A;
        pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true);
    }
}

Saya harap ini membantu orang lain.

Sulfkain
sumber
Ini salah. Ketika onCreate dipanggil dengan Bundel, fragmen-fragmen itu tidak akan sama dengan yang akan ditampilkan di layar dan Anda membocorkan yang lama, kecuali jika Anda menggunakan setRetainInstance. Dan jika ActivityManager "menyimpan" Aktivitas Anda, karena Fragmen tidak Serializable atau Parcelable, ketika pengguna kembali ke Aktivitas Anda, itu akan macet.
sergio91pt
-1

Saya akan merekomendasikan jangan menggunakan backstack berdasarkan HashMap> ada banyak bug dalam mode "jangan simpan aktivitas". Itu tidak akan benar mengembalikan keadaan jika Anda dalam tumpukan fragmen. Dan juga akan di-crack dalam fragmen peta bersarang (dengan pengecualian: Fragmen tidak ada tampilan untuk ID). Coz HashMap> setelah aplikasi latar \ foreground akan menjadi nol

Saya mengoptimalkan kode di atas untuk bekerja dengan backstack fragmen

Itu adalah TabView bagian bawah

Kegiatan utama Kelas

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import com.strikersoft.nida.R;
import com.strikersoft.nida.abstractActivity.BaseActivity;
import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment;
import com.strikersoft.nida.screens.tags.searchTab.SearchFragment;
import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment;

public class TagsActivity extends BaseActivity {
    public static final String M_CURRENT_TAB = "M_CURRENT_TAB";
    private TabHost mTabHost;
    private String mCurrentTab;

    public static final String TAB_TAGS = "TAB_TAGS";
    public static final String TAB_MAP = "TAB_MAP";
    public static final String TAB_SETTINGS = "TAB_SETTINGS";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        getActionBar().hide();
        setContentView(R.layout.tags_activity);

        mTabHost = (TabHost) findViewById(android.R.id.tabhost);

        mTabHost.setup();

        if (savedInstanceState != null) {
            mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB);
            initializeTabs();
            mTabHost.setCurrentTabByTag(mCurrentTab);
            /*
            when resume state it's important to set listener after initializeTabs
            */
            mTabHost.setOnTabChangedListener(listener);
        } else {
            mTabHost.setOnTabChangedListener(listener);
            initializeTabs();
        }
    }

    private View createTabView(final int id, final String text) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        TextView textView = (TextView) view.findViewById(R.id.tab_text);
        textView.setText(text);
        return view;
    }

    /*
    create 3 tabs with name and image
    and add it to TabHost
     */
    public void initializeTabs() {

        TabHost.TabSpec spec;

        spec = mTabHost.newTabSpec(TAB_TAGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags)));
        mTabHost.addTab(spec);

        spec = mTabHost.newTabSpec(TAB_MAP);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map)));
        mTabHost.addTab(spec);


        spec = mTabHost.newTabSpec(TAB_SETTINGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings)));
        mTabHost.addTab(spec);

    }

    /*
    first time listener will be trigered immediatelly after first: mTabHost.addTab(spec);
    for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener
    */
    TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
        public void onTabChanged(String tabId) {

            mCurrentTab = tabId;

            if (tabId.equals(TAB_TAGS)) {
                pushFragments(SearchFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_MAP)) {
                pushFragments(MapContainerFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_SETTINGS)) {
                pushFragments(SettingsFragment.getInstance(), false,
                        false, null);
            }

        }
    };

/*
Example of starting nested fragment from another fragment:

Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac());
                TagsActivity tAct = (TagsActivity)getActivity();
                tAct.pushFragments(newFragment, true, true, null);
 */
    public void pushFragments(Fragment fragment,
                              boolean shouldAnimate, boolean shouldAdd, String tag) {
        FragmentManager manager = getFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        if (shouldAnimate) {
            ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
                    R.animator.fragment_slide_left_exit,
                    R.animator.fragment_slide_right_enter,
                    R.animator.fragment_slide_right_exit);
        }
        ft.replace(R.id.realtabcontent, fragment, tag);

        if (shouldAdd) {
            /*
            here you can create named backstack for realize another logic.
            ft.addToBackStack("name of your backstack");
             */
            ft.addToBackStack(null);
        } else {
            /*
            and remove named backstack:
            manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE);
            or remove whole:
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
             */
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
        ft.commit();
    }

    /*
    If you want to start this activity from another
     */
    public static void startUrself(Activity context) {
        Intent newActivity = new Intent(context, TagsActivity.class);
        newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newActivity);
        context.finish();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(M_CURRENT_TAB, mCurrentTab);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed(){
        super.onBackPressed();
    }
}

tags_activity.xml

<

?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>
        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:background="@drawable/bg_main_app_gradient"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <TabWidget
            android:id="@android:id/tabs"
            android:background="#EAE7E1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>
    </LinearLayout>
</TabHost>

tags_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tabsLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/bg_tab_gradient"
    android:gravity="center"
    android:orientation="vertical"
    tools:ignore="contentDescription" >

    <ImageView
        android:id="@+id/tab_icon"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/tab_text"
        android:layout_marginBottom="3dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/tab_text_color"/>

</LinearLayout>

masukkan deskripsi gambar di sini

Flinbor
sumber