Praktik terbaik untuk fragmen bersarang di Android 4.0, 4.1 (<4.2) tanpa menggunakan support library

115

Saya sedang menulis aplikasi untuk tablet 4.0 dan 4.1, yang saya tidak ingin menggunakan pustaka dukungan (jika tidak diperlukan) tetapi hanya karena itu api 4.x.

Jadi platform target saya didefinisikan dengan sangat baik sebagai:> = 4.0 dan <= 4.1

Aplikasi memiliki tata letak multi-panel (dua fragmen, satu fragmen kecil di kiri, satu fragmen konten di kanan) dan bilah tindakan dengan tab.

Mirip dengan ini:

masukkan deskripsi gambar di sini

Mengklik tab pada bilah tindakan akan mengubah fragmen 'luar', dan fragmen bagian dalam adalah fragmen dengan dua fragmen bersarang (1. fragmen daftar kiri kecil, 2. fragmen konten lebar).

Sekarang saya bertanya-tanya apa praktik terbaik untuk mengganti fragmen dan terutama fragmen bersarang. ViewPager adalah bagian dari pustaka dukungan, tidak ada alternatif 4.x asli untuk kelas ini. Tampak seperti 'deprecated' dalam pengertian saya. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Kemudian saya membaca catatan rilis untuk Android 4.2, mengenai ChildFragmentManager, mana yang cocok, tetapi saya menargetkan 4.0 dan 4.1, jadi ini juga tidak dapat digunakan.

ChildFragmentManager hanya tersedia di 4.2

Sayangnya, hampir tidak ada contoh bagus di luar sana yang menunjukkan praktik terbaik untuk penggunaan fragmen tanpa pustaka dukungan, bahkan di seluruh panduan pengembang Android; dan terutama tidak tentang fragmen bersarang.

Jadi saya bertanya-tanya: apakah tidak mungkin untuk menulis 4.1 aplikasi dengan fragmen bersarang tanpa menggunakan pustaka dukungan dan semua yang menyertainya? (perlu menggunakan FragmentActivity, bukan Fragment, dll.?) Atau apa praktik terbaiknya?


Masalah yang saya hadapi saat ini dalam pengembangan adalah pernyataan ini:

Android Support Library juga sekarang mendukung fragmen bersarang, sehingga Anda bisa mengimplementasikan desain fragmen bertingkat pada Android 1.6 dan yang lebih tinggi.

Catatan: Anda tidak bisa memekarkan tata letak menjadi fragmen jika tata letak itu menyertakan file <fragment>. Fragmen bertingkat hanya didukung saat ditambahkan ke fragmen secara dinamis.

Karena saya menempatkan define fragmen bersarang di XML, yang ternyata menyebabkan kesalahan seperti:

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

Saat ini, saya menyimpulkan sendiri: bahkan pada 4.1, ketika saya bahkan tidak ingin menargetkan platform 2.x, fragmen bersarang seperti yang ditunjukkan pada tangkapan layar tidak mungkin dilakukan tanpa pustaka dukungan.

(Ini mungkin sebenarnya lebih merupakan entri wiki daripada pertanyaan, tapi mungkin orang lain telah mengelolanya sebelumnya).

Memperbarui:

Jawaban yang membantu ada di: Fragment Inside Fragment

Mathias Conradt
sumber
22
Anda memiliki tiga opsi: 1. Targetkan hanya 4.2 dengan fragmen bersarang asli. 2. Target 4.x dengan fragmen bertingkat dari support library 3. Jangan gunakan fragmen bertingkat untuk skenario target platform lainnya. Ini harus menjawab pertanyaan Anda. Selain itu, Anda tidak dapat menggunakan fragmen bersarang yang disematkan dalam tata letak xml, semuanya harus ditambahkan dalam kode. Hampir tidak ada contoh bagus di luar sana yang menunjukkan praktik terbaik untuk penggunaan fragmen tanpa pustaka dukungan - kerangka kerja fragmen dukungan mereplikasi yang asli sehingga setiap contoh harus berfungsi dengan baik.
Luksprog
@Luksprog Terima kasih atas komentar Anda. Saya lebih suka solusi Anda 2, dan framen bekerja dengan baik di pustaka dukungan, tetapi Tab di ActionBar tidak - afaik, saya perlu menggunakan ActionBarSherlock, tetapi tab tidak akan terintegrasi di ActionBar saat itu tetapi hanya di bawah (yang isn ' t diperlukan untuk 4.x). Dan ActionBar.TabListener hanya mendukung Fragmen dari android.app.Fragment, bukan dari pustaka dukungan.
Mathias Conradt
2
Saya tidak terbiasa dengan aplikasi Kontak di tab Galaxy tetapi perlu diingat bahwa Anda selalu dapat menghadapi implementasi khusus ActionBar(dibangun di rumah oleh Samsung). Lihat lebih dekat ActionBarSherlock, ia memiliki tab di ActionBar jika ada ruang.
Luksprog
4
@Luksprog Saya yakin Anda telah memberikan satu-satunya jawaban yang dapat diberikan, apakah Anda akan berbaik hati untuk memasukkannya sebagai jawaban yang tepat.
Warpzit
1
@Pork Alasan utama saya untuk pertanyaan ini adalah: apakah ada solusi untuk fragmen bersarang tanpa harus menggunakan pustaka dukungan dan semua elemen tampilan lainnya. Artinya, jika saya beralih ke pustaka dukungan, saya akan menggunakan FragmentActivity, bukan Fragment. Tetapi saya ingin menggunakan Fragment, yang saya inginkan hanyalah pengganti Fragmen Bersarang , tetapi tidak semua komponen v4. Yaitu melalui pustaka sumber terbuka lainnya, dll. Di luar sana. Misalnya, tangkapan layar di atas berjalan pada 4.0, dan saya bertanya-tanya apakah mereka menggunakan ABS, SupportLib, atau yang lainnya.
Mathias Conradt

Jawaban:

60

Batasan

Jadi, menumpuk fragmen di dalam fragmen lain tidak dimungkinkan dengan xml apa pun versi FragmentManager Anda gunakan.

Jadi, Anda harus menambahkan fragmen melalui kode, ini mungkin tampak seperti masalah, tetapi dalam jangka panjang membuat tata letak Anda menjadi sangat fleksibel.

Jadi bersarang tanpa menggunakan getChildFragmentManger? Esensi di baliknyachildFragmentManager adalah bahwa ia menunda pemuatan sampai transaksi fragmen sebelumnya telah selesai. Dan tentu saja itu hanya didukung secara alami di 4.2 atau pustaka dukungan.

Bersarang tanpa ChildManager - Solusi

Solusi, Tentu! Saya telah melakukan ini untuk waktu yang lama sekarang, (sejakViewPager diumumkan).

Lihat di bawah; Ini adalah Fragmentyang menunda pemuatan, jadiFragment s dapat dimuat di dalamnya.

Ini cukup sederhana, Handlerini adalah kelas yang sangat berguna, secara efektif penangan menunggu ruang untuk dieksekusi pada utas utama setelah transaksi fragmen saat ini selesai dilakukan (karena fragmen mengganggu UI yang mereka jalankan di utas utama).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

Saya tidak akan menganggapnya sebagai 'praktik terbaik', tetapi saya memiliki aplikasi langsung menggunakan peretasan ini dan saya belum mengalami masalah apa pun dengannya.

Saya juga menggunakan metode ini untuk menyematkan pager tampilan - https://gist.github.com/chrisjenx/3405429

Chris.Jenkins
sumber
Bagaimana Anda menangani tata letak fragmen bersarang?
pablisco
Satu-satunya cara saya bisa melihat ini bekerja adalah dengan menggunakan CustomLayoutInflater, saat Anda menemukan fragmentelemen, Anda akan menimpa implementasi super dan mencoba mengurai / mengembangkannya sendiri. Tapi itu akan menjadi BANYAK usaha, Nah di luar cakupan pertanyaan StackOverflow.
Chris.Jenkins
Hai, ada yang bisa membantu saya dalam masalah ini ?? Saya benar-benar terjebak .. stackoverflow.com/questions/32240138/…
Nicks
2

Cara terbaik untuk melakukan ini di pra-API 17 adalah dengan tidak melakukannya sama sekali. Mencoba menerapkan perilaku ini akan menimbulkan masalah. Namun itu tidak berarti bahwa itu tidak dapat dipalsukan secara meyakinkan menggunakan API 14 saat ini. Apa yang saya lakukan adalah sebagai berikut:

1 - lihat komunikasi antar fragmen http://developer.android.com/training/basics/fragments/communicating.html

2 - pindahkan tata letak xml FrameLayout Anda dari Fragmen yang ada ke tata letak Aktivitas dan sembunyikan dengan memberikan ketinggian 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3 - Implementasikan antarmuka di Fragmen induk

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

@Override
public void onActivityCreated(Bundle savedInstanceState) 
{
    super.onActivityCreated(savedInstanceState);

    mCallback.showResults();
}

@Override
public void onPause()
{
    super.onPause();

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4 - Implementasikan antarmuka di Aktivitas induk

public class YourActivity extends Activity mengimplementasikan yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5 - Selamat menikmati, dengan metode ini Anda mendapatkan fungsi fluid yang sama seperti fungsi getChildFragmentManager () di lingkungan pra-API 17. Seperti yang Anda ketahui, fragmen turunan sebenarnya bukan lagi turunan dari fragmen induk tetapi sekarang menjadi turunan aktivitas, hal ini benar-benar tidak dapat dihindari.

Alex
sumber
1

Saya harus berurusan dengan masalah yang tepat ini karena kombinasi NavigationDrawer, TabHost, dan ViewPager yang mengalami komplikasi dengan penggunaan pustaka dukungan karena TabHost. Dan kemudian saya juga harus mendukung min API JellyBean 4.1, jadi menggunakan fragmen bersarang dengan getChildFragmentManager bukanlah pilihan.

Jadi masalah saya bisa disaring ke ...

TabHost (untuk level teratas)
+ ViewPager (hanya untuk salah satu fragmen tab tingkat atas)
= kebutuhan untuk Fragmen Bersarang (yang tidak didukung JellyBean 4.1)

Solusi saya adalah menciptakan ilusi fragmen bersarang tanpa benar-benar membuat fragmen bersarang. Saya melakukan ini dengan membuat aktivitas utama menggunakan TabHost DAN ViewPager untuk mengelola dua Tampilan bersaudara yang visibilitasnya dikelola dengan mengubah layout_weight antara 0 dan 1.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

Ini secara efektif memungkinkan "Fragmen Tersarang" palsu saya untuk beroperasi sebagai tampilan independen selama saya mengelola bobot tata letak yang relevan secara manual.

Inilah activity_main.xml saya:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        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:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

Perhatikan bahwa "@ + id / pager" dan "@ + id / container" adalah saudara dengan 'android: layout_weight = "0.5"' dan 'android: layout_height = "0dp"'. Ini agar saya dapat melihatnya di pratinjau untuk ukuran layar apa pun. Bobotnya akan dimanipulasi dalam kode selama runtime.

Kain Shin
sumber
Hai, saya penasaran mengapa Anda memilih menggunakan TabHost daripada ActionBar dengan Tab? Saya sendiri beralih dari TabHost ke hanya ActionBar, dan kode saya menjadi lebih bersih dan lebih ringkas ...
IgorGanapolsky
Sejauh yang saya ingat, salah satu kelemahan menggunakan tab di ActionBar adalah ia memutuskan secara otomatis untuk menampilkannya sebagai menu drop-down spinner (dalam kasus layar kecil) dan itu tidak baik untuk saya. Tapi saya tidak 100% yakin.
WindRider
@Igor, saya membaca di suatu tempat di sini SObahwa menggunakan ActionBartab dengan a Navigation Drawertidak baik karena secara otomatis akan menempatkan tab di atas tampilan laci Anda. Maaf, saya tidak memiliki link untuk membuat cadangan ini.
Azurespot
1
@Noni. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Apakah Anda sama sekali mengikuti perkembangan Android?
IgorGanapolsky
1
Wow, terima kasih untuk tautannya @Igor! Saya akan memeriksanya dengan pasti. Saya masih pemula, jadi memiliki sejuta hal lain untuk dipelajari dengan Android, tapi yang satu ini sepertinya bagus! Terima kasih lagi.
Azurespot
1

Berdasarkan jawaban @ Chris.Jenkins, ini adalah solusi yang telah bekerja dengan baik untuk saya, untuk menghapus fragmen selama peristiwa siklus hidup (yang memiliki kecenderungan untuk memunculkan IllegalStateExceptions). Ini menggunakan kombinasi pendekatan Handler, dan pemeriksaan Activity.isFinishing () (jika tidak maka akan memunculkan kesalahan untuk "Tidak dapat melakukan tindakan ini setelah onSaveInstanceState).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

Pemakaian:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
HelloImKevo
sumber
1

Meskipun OP mungkin memiliki keadaan khusus yang mencegahnya menggunakan Pustaka Dukungan, kebanyakan orang harus menggunakannya. Dokumentasi Android merekomendasikannya, dan itu akan membuat aplikasi Anda tersedia untuk audiens seluas mungkin.

Dalam jawaban lengkap saya di sini, saya membuat contoh yang mendemonstrasikan cara menggunakan fragmen bersarang dengan pustaka dukungan.

masukkan deskripsi gambar di sini

Suragch
sumber
Saya adalah OP. Alasan untuk tidak menggunakan lib dukungan adalah karena ini adalah aplikasi internal perusahaan, dengan perangkat keras yang digunakan dengan jelas didefinisikan sebagai> = 4.0 dan <= 4.1. Tidak perlu menjangkau khalayak luas, itu adalah staf internal dan tidak ada niat untuk menggunakan aplikasi di luar perusahaan. Satu-satunya alasan dukungan lib adalah agar kompatibel ke belakang - tetapi orang akan berharap bahwa semua yang dapat Anda lakukan dengan pustaka dukungan, Anda harus dapat mencapai "secara asli" tanpanya. Mengapa versi "asli" yang lebih tinggi memiliki fitur yang lebih sedikit daripada pustaka dukungan yang hanya bertujuan untuk kompatibel ke bawah.
Mathias Conradt
1
Namun demikian tentu saja Anda dapat menggunakan pustaka dukungan dan saya juga dapat melakukannya. Hanya tidak mengerti mengapa Google menawarkan fitur HANYA di pustaka dukungan tetapi tidak di luar, atau mengapa mereka bahkan menyebutnya pustaka dukungan dan tidak menjadikannya standar keseluruhan, jika itu adalah praktik terbaik. Berikut adalah artikel bagus tentang pustaka dukungan: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt
@Mathias, artikel bagus.
Suragch