Best practice per i frammenti nidificati in Android 4.0, 4.1 (<4.2) senza utilizzare la libreria di supporto


115

Sto scrivendo un'app per tablet 4.0 e 4.1, per la quale non voglio utilizzare le librerie di supporto (se non necessarie) ma solo l'api 4.x quindi.

Quindi la mia piattaforma di destinazione è molto ben definita come:> = 4.0 e <= 4.1

L'app ha un layout multi-riquadro (due frammenti, uno piccolo a sinistra, un frammento di contenuto a destra) e una barra delle azioni con schede.

Simile a questo:

inserisci qui la descrizione dell'immagine

Facendo clic su una scheda nella barra delle azioni si modifica il frammento "esterno" e il frammento interno diventa quindi un frammento con due frammenti nidificati (1. piccolo frammento di elenco a sinistra, 2. ampio frammento di contenuto).

Ora mi chiedo quale sia la migliore pratica per sostituire i frammenti e in particolare i frammenti nidificati. ViewPager fa parte della libreria di supporto, non esiste un'alternativa 4.x nativa per questa classe. Sembra essere "deprecato" nel mio senso. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Quindi ho letto le note di rilascio per Android 4.2, a proposito ChildFragmentManager, che sarebbe una buona misura, ma sto prendendo di mira 4.0 e 4.1, quindi non può essere utilizzato neanche questo.

ChildFragmentManager è disponibile solo in 4.2

Sfortunatamente, non ci sono quasi buoni esempi là fuori che mostrano le migliori pratiche per l'utilizzo di frammenti senza la libreria di supporto, anche nelle intere guide per sviluppatori Android; e soprattutto niente per quanto riguarda i frammenti annidati.

Quindi mi chiedo: semplicemente non è possibile scrivere app 4.1 con frammenti nidificati senza utilizzare la libreria di supporto e tutto ciò che viene fornito con essa? (è necessario utilizzare FragmentActivity invece di Fragment, ecc.?) O quale sarebbe la migliore pratica?


Il problema che sto attualmente riscontrando nello sviluppo è esattamente questa affermazione:

La libreria di supporto Android ora supporta anche i frammenti nidificati, quindi puoi implementare progetti di frammenti nidificati su Android 1.6 e versioni successive.

Nota: non è possibile espandere un layout in un frammento quando tale layout include un file <fragment>. I frammenti nidificati sono supportati solo se aggiunti dinamicamente a un frammento.

Perché ho messo definire i frammenti nidificati in XML, che apparentemente causa un errore come:

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_

Al momento, concludo da solo: anche su 4.1, quando non voglio nemmeno targetizzare la piattaforma 2.x, i frammenti nidificati come mostrato nello screenshot non sono possibili senza la libreria di supporto.

(Questa potrebbe effettivamente essere più una voce wiki che una domanda, ma forse qualcun altro l'ha gestita prima).

Aggiornare:

Una risposta utile è a: Fragment Inside Fragment


22
Sono disponibili tre opzioni: 1. Target solo 4.2 con frammenti nidificati nativi. 2. Target 4.x con frammenti nidificati dalla libreria di supporto 3. Non utilizzare frammenti nidificati per altri scenari di destinazione della piattaforma. Questo dovrebbe rispondere alla tua domanda. Inoltre, non puoi usare frammenti annidati incorporati nel layout xml, tutti devono essere aggiunti nel codice. non ci sono quasi buoni esempi là fuori che mostrano le migliori pratiche per l'utilizzo dei frammenti senza la libreria di supporto : il framework dei frammenti di supporto replica quello nativo, quindi qualsiasi esempio dovrebbe funzionare in entrambi i casi.
Luksprog

@ Luksprog Grazie per i tuoi commenti. Preferisco la tua soluzione 2 e i frament funzionano bene nella libreria di supporto, ma le schede nella ActionBar no - afaik, avrei bisogno di usare ActionBarSherlock, ma le schede non sarebbero integrate nella ActionBar allora ma solo sotto (che non è t necessario per 4.x). E ActionBar.TabListener supporta solo i frammenti da android.app.Fragment, non dalla libreria di supporto.
Mathias Conradt

2
Non ho familiarità con l'app Contatti nella scheda Galaxy, ma tieni presente che potresti sempre affrontare un'implementazione personalizzata di ActionBar(costruita in casa da Samsung). Dai un'occhiata più da vicino ad ActionBarSherlock, ha le schede nell'ActionBar se c'è spazio.
Luksprog

4
@ Luksprog Credo che tu abbia già fornito l'unica risposta che c'è da dare, saresti così gentile da inserirla come una risposta corretta.
Warpzit

1
@Pork Il motivo principale della domanda è: esistono soluzioni alternative per i frammenti nidificati senza dover utilizzare la libreria di supporto e tutti gli altri elementi di visualizzazione. Significa che se passo alla libreria di supporto, utilizzerei FragmentActivity invece di Fragment. Ma voglio usare Fragment, tutto quello che voglio è un sostituto per Nested Fragments , ma non tutti i componenti v4. Cioè tramite altre librerie open source, ecc. Là fuori. Ad esempio, lo screenshot qui sopra gira su 4.0 e mi chiedo se stanno usando ABS, SupportLib o qualsiasi altra cosa.
Mathias Conradt

Risposte:


60

limitazioni

Quindi l'annidamento dei frammenti all'interno di un altro frammento non è possibile con xml indipendentemente dalla versione FragmentManagerutilizzata.

Quindi devi aggiungere frammenti tramite codice, questo potrebbe sembrare un problema, ma a lungo andare rende i tuoi layout superflessibili.

Quindi nidificare senza usare getChildFragmentManger? L'essenza childFragmentManagerè che rimanda il caricamento fino al termine della transazione del frammento precedente. E ovviamente era supportato naturalmente solo in 4.2 o nella libreria di supporto.

Annidamento senza ChildManager - Soluzione

Soluzione, certo! Lo sto facendo da molto tempo (da quando è ViewPagerstato annunciato).

Vedi sotto; Questo è un Fragmentche differisce il caricamento, quindi Fragmenti messaggi possono essere caricati al suo interno.

È piuttosto semplice, Handlerè una classe davvero molto utile, in effetti il ​​gestore attende che uno spazio venga eseguito sul thread principale dopo che la transazione del frammento corrente ha terminato il commit (poiché i frammenti interferiscono con l'interfaccia utente che vengono eseguiti sul thread principale).

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

Non lo considererei una 'best practice', ma ho app live che utilizzano questo hack e devo ancora avere problemi con esso.

Uso anche questo metodo per incorporare i cercapersone di visualizzazione: https://gist.github.com/chrisjenx/3405429


Come gestisci i frammenti nidificati del layout?
pablisco

L'unico modo in cui ho potuto vedere questo funzionamento è utilizzare CustomLayoutInflater, quando ti imbatti fragmentnell'elemento, sovrascriveresti la super implementazione e proverai ad analizzarlo / gonfiarlo da solo. Ma questo sarà MOLTO impegno, ben al di fuori dell'ambito di una domanda StackOverflow.
Chris.Jenkins

Ciao, qualcuno può aiutarmi in questo problema ?? Sono davvero bloccato .. stackoverflow.com/questions/32240138/…
Nicks

2

Il modo migliore per farlo in pre-API 17 è non farlo affatto. Il tentativo di implementare questo comportamento causerà problemi. Tuttavia questo non vuol dire che non possa essere simulato in modo convincente utilizzando l'attuale API 14. Quello che ho fatto è stato il seguente:

1 - guarda la comunicazione tra i frammenti http://developer.android.com/training/basics/fragments/communicating.html

2 - sposta il layout xml FrameLayout dal frammento esistente al layout Attività e nascondilo dando un'altezza di 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 - Implementa l'interfaccia nel frammento genitore

    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 - Implementa l'interfaccia nell'attività genitore

public class YourActivity extends Activity implementa 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 - Divertiti, con questo metodo ottieni la stessa funzionalità fluida della funzione getChildFragmentManager () in un ambiente pre-API 17. Come avrai notato, il frammento figlio non è più realmente un figlio del frammento genitore ma ora un figlio dell'attività, questo non può essere evitato.


1

Ho dovuto affrontare questo problema esatto a causa di una combinazione di NavigationDrawer, TabHost e ViewPager che presentava complicazioni con l'utilizzo della libreria di supporto a causa di TabHost. E poi ho anche dovuto supportare l'API min di JellyBean 4.1, quindi l'utilizzo di frammenti nidificati con getChildFragmentManager non era un'opzione.

Quindi il mio problema può essere distillato in ...

TabHost (per il livello superiore)
+ ViewPager (solo per uno dei frammenti a schede di primo livello)
= necessità di frammenti annidati (che JellyBean 4.1 non supporterà)

La mia soluzione era creare l'illusione di frammenti annidati senza effettivamente annidare frammenti. L'ho fatto facendo in modo che l'attività principale utilizzi TabHost E ViewPager per gestire due viste fratelli la cui visibilità è gestita alternando layout_weight tra 0 e 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);

Ciò ha effettivamente consentito al mio falso "frammento annidato" di funzionare come una visualizzazione indipendente, purché gestissi manualmente i pesi del layout pertinenti.

Ecco la mia attività_main.xml:

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

Tieni presente che "@ + id / pager" e "@ + id / container" sono fratelli con "android: layout_weight =" 0.5 "" e "android: layout_height =" 0dp "". Questo è così che posso vederlo nell'anteprima per qualsiasi dimensione dello schermo. I loro pesi verranno comunque manipolati nel codice durante il runtime.


Ciao, sono curioso di sapere perché hai scelto di utilizzare TabHost invece di ActionBar con le schede? Io stesso sono passato da TabHost a ActionBar e il mio codice è diventato più pulito e compatto ...
IgorGanapolsky

Per quanto ricordo, uno svantaggio dell'utilizzo delle schede nella ActionBar è che decide automaticamente di mostrarle come un menu a discesa di selezione (in caso di uno schermo piccolo) e non è stato un bene per me. Ma non sono sicuro al 100%.
WindRider

@ Igor, ho letto da qualche parte qui SOche usare le ActionBarschede con a Navigation Drawernon è buono perché posizionerà automaticamente le schede sulla vista del tuo cassetto. Mi spiace, non ho il link per eseguire il backup.
Azurespot


1
Wow, grazie per il link @Igor! Lo controllerò di sicuro. Sono ancora un principiante, quindi ho un milione di altre cose da imparare con Android, ma questo sembra un gioiello! Grazie ancora.
Azurespot

1

Basandosi sulla risposta di @ Chris.Jenkins, questa è la soluzione che ha funzionato bene per me, per la rimozione di frammenti durante gli eventi del ciclo di vita (che hanno la tendenza a lanciare IllegalStateExceptions). Questo utilizza una combinazione dell'approccio Handler e un controllo Activity.isFinishing () (altrimenti genererà un errore per "Impossibile eseguire questa azione dopo 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();
                }
            }
        });
    }
}

Uso:

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

1

Sebbene l'OP possa avere circostanze speciali che gli impediscono di utilizzare la libreria di supporto, la maggior parte delle persone dovrebbe usarla. La documentazione di Android lo consiglia e renderà la tua app disponibile al più vasto pubblico possibile.

Nella mia risposta più completa qui ho fatto un esempio che dimostra come utilizzare i frammenti nidificati con la libreria di supporto.

inserisci qui la descrizione dell'immagine


Ero l'OP. Il motivo per non utilizzare la libreria di supporto era perché si trattava di un'app interna all'azienda, in cui l'hardware utilizzato era chiaramente definito come> = 4.0 e <= 4.1. Non c'era bisogno di raggiungere un vasto pubblico, era personale interno e nessuna intenzione di utilizzare l'app all'esterno dell'azienda. L'unico motivo della libreria di supporto è essere retrocompatibile - ma ci si aspetterebbe che tutto ciò che si può fare con la libreria di supporto, dovrebbe essere in grado di ottenere "nativamente" senza di essa. Perché una versione superiore "nativa" dovrebbe avere meno funzionalità di una libreria di supporto il cui unico scopo è solo quello di essere compatibile con le versioni precedenti.
Mathias Conradt

1
Tuttavia, ovviamente, potresti usare la libreria di supporto e anch'io avrei potuto. Semplicemente non capivo perché Google offre funzionalità SOLO nella libreria di supporto ma non all'esterno, o perché l'avrebbero persino chiamata libreria di supporto e non ne avrebbero fatto lo standard generale, se è comunque la migliore pratica. Ecco un buon articolo sulla libreria di supporto: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@ Mathias, bell'articolo.
Suragch
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.