web-dev-qa-db-fra.com

Meilleure pratique pour les fragments imbriqués dans Android 4.0, 4.1 (<4.2) sans utiliser la bibliothèque de support

J'écris une application pour les tablettes 4.0 et 4.1, pour laquelle je ne veux pas utiliser les bibliothèques de support (sinon nécessaire), mais 4. x api seulement donc.

Donc, ma plate-forme cible est très bien définie comme suit:> = 4.0 et <= 4.1

L'application dispose d'une disposition à plusieurs volets (deux fragments, un petit à gauche, un fragment de contenu à droite) et d'une barre d'action avec des onglets.

Semblable à ceci:

enter image description here

Un clic sur un onglet de la barre d'action modifie le fragment "externe" et le fragment interne est alors un fragment avec deux fragments imbriqués (1. petit fragment de liste de gauche, 2. fragment de contenu large).

Je me demande maintenant quelle est la meilleure pratique pour remplacer les fragments et en particulier les fragments imbriqués. ViewPager fait partie de la bibliothèque de support, il n’existe pas d’alternative 4.x native pour cette classe. Semble être "obsolète" dans mon sens. - http://developer.Android.com/reference/Android/support/v4/view/ViewPager.html

Ensuite, j'ai lu les notes de publication de Android 4.2, en ce qui concerne ChildFragmentManager, ce qui serait un bon choix, mais je cible les versions 4.0 et 4.1, donc cela ne peut pas être utilisé non plus. .

ChildFragmentManager est uniquement disponible en 4.2

Malheureusement, il n’existe guère de bons exemples montrant les meilleures pratiques en matière d’utilisation de fragments sans la bibliothèque de support, même dans les guides de développement Android); et surtout rien concernant les fragments imbriqués.

Je me pose donc la question suivante: est-il tout simplement impossible d'écrire des applications 4.1 avec des fragments imbriqués sans utiliser la bibliothèque de support et tout ce qui va avec? (besoin d'utiliser FragmentActivity au lieu de Fragment, etc.?) Ou quelle serait la meilleure pratique?


Le problème que je suis actuellement dans le développement est exactement cette déclaration:

La bibliothèque de support de Android) prend également en charge les fragments imbriqués. Vous pouvez donc implémenter des conceptions de fragments imbriquées sur Android 1.6 et versions ultérieures.

Remarque: Vous ne pouvez pas gonfler une mise en page dans un fragment lorsque cette mise en page inclut un <fragment>. Les fragments imbriqués ne sont pris en charge que s'ils sont ajoutés à un fragment de manière dynamique.

Parce que je mets définir les fragments imbriqués en XML, ce qui provoque apparemment une erreur comme:

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_

Pour le moment, je conclus moi-même: même en 4.1, lorsque je ne souhaite même pas cibler la plate-forme 2.x, les fragments imbriqués, comme indiqué sur la capture d'écran, ne sont pas possibles sans la bibliothèque de support.

(Cela pourrait en fait être plus une entrée de wiki qu'une question, mais peut-être que quelqu'un d'autre l'a déjà fait auparavant).

Mise à jour:

Une réponse utile est à: Fragment Inside Fragment

115
Mathias Conradt

Limites

Donc, imbriquer des fragments dans un autre fragment n'est pas possible avec XML, quelle que soit la version de FragmentManager que vous utilisez.

Vous devez donc ajouter des fragments via le code, cela peut sembler être un problème, mais à long terme, il rend vos mises en page superflues.

Alors imbriquer sans utiliser getChildFragmentManger? L'essence de childFragmentManager réside dans le fait qu'il différera le chargement jusqu'à la fin de la transaction de fragment précédente. Et bien sûr, il n’était naturellement pris en charge que dans la version 4.2 ou dans la bibliothèque de support.

Imbrication sans ChildManager - Solution

Solution, bien sûr! Je le fais depuis longtemps (depuis l'annonce du ViewPager).

Voir ci-dessous; Ceci est un Fragment qui diffère le chargement, donc Fragments peut être chargé à l'intérieur.

C'est assez simple, le Handler est une classe vraiment très pratique, le gestionnaire attend effectivement un espace à exécuter sur le thread principal une fois que la transaction de fragment en cours a été validée (les fragments interfèrent avec l'interface utilisateur qu'ils exécutent sur le fil principal).

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

Je ne considérerais pas cela comme une "bonne pratique", mais j'ai des applications en direct qui utilisent ce hack et je n'ai pas encore eu de problèmes avec cela.

J'utilise également cette méthode pour incorporer des pagers de vue - https://Gist.github.com/chrisjenx/3405429

60
Chris.Jenkins

La meilleure façon de faire cela dans la pré-API 17 est de ne pas le faire du tout. Essayer d'implémenter ce comportement va causer des problèmes. Cependant, cela ne veut pas dire qu'il ne peut pas être simulé de manière convaincante à l'aide de l'actuelle API 14. Voici ce que j'ai fait:

1 - Regardez la communication entre fragments http://developer.Android.com/training/basics/fragments/communicating.html

2 - déplacez votre mise en page xml FrameLayout de votre fragment existant vers la mise en page Activité et masquez-la en donnant une hauteur de 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 - Implémenter l'interface dans le fragment parent

    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 - Implémenter l'interface dans l'activité parente

classe publique YourActivity extend Activity met en oeuvre 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 - Enjoy, avec cette méthode, vous obtenez la même fonctionnalité fluide que la fonction getChildFragmentManager () dans un environnement pré-API 17. Comme vous avez peut-être remarqué que le fragment enfant n'est plus vraiment un enfant du fragment parent mais maintenant un enfant de l'activité, cela ne peut vraiment pas être évité.

2
Alex

J'ai dû faire face à ce problème en raison d'une combinaison de NavigationDrawer, TabHost et ViewPager, qui a entraîné des complications avec l'utilisation de la bibliothèque de support à cause de TabHost. Et puis je devais aussi supporter min API de JellyBean 4.1, donc utiliser des fragments imbriqués avec getChildFragmentManager n'était pas une option.

Donc, mon problème peut être distillé à ...

TabHost (pour le niveau supérieur) 
 + ViewPager (pour un seul des fragments tabulés de niveau supérieur) 
 = Besoin de fragments imbriqués (non pris en charge par JellyBean 4.1)

Ma solution a été de créer l'illusion de fragments imbriqués sans les fragments imbriqués. Pour ce faire, l’activité principale a utilisé TabHost AND ViewPager pour gérer deux vues jumelles dont la visibilité est gérée en basculant layout_weight entre 0 et 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);

Cela a effectivement permis à mon faux "Fragment imbriqué" de fonctionner comme une vue indépendante à condition que je gère manuellement les poids de présentation pertinents.

Voici mon activity_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>

Notez que "@ + id/pager" et "@ + id/conteneur" sont des frères et sœurs avec "Android: layout_weight =" 0.5 "" et "Android: layout_height =" 0dp "". C'est pour que je puisse le voir dans l'aperçu pour n'importe quelle taille d'écran. Leurs poids seront manipulés dans le code pendant l'exécution, de toute façon.

1
Kain Shin

Bien que le PO puisse avoir des circonstances spéciales qui l’empêchent d’utiliser la bibliothèque de support, la plupart des gens doivent l’utiliser. La documentation de Android le recommande et rendra votre application disponible au plus grand nombre possible.

Dans ma réponse plus complète ici J'ai créé un exemple montrant comment utiliser des fragments imbriqués avec la bibliothèque de support.

enter image description here

1
Suragch

S'appuyant sur la réponse de @ Chris.Jenkins, voici la solution qui fonctionne bien pour moi, pour supprimer des fragments lors d'événements de cycle de vie (qui ont tendance à déclencher IllegalStateExceptions). Ceci utilise une combinaison de l'approche Handler et d'une vérification Activity.isFinishing () (sinon, une erreur sera générée pour "Impossible d'effectuer cette action après 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();
                }
            }
        });
    }
}

Usage:

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