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


115

J'écris une application pour les tablettes 4.0 et 4.1, pour lesquelles je ne souhaite pas utiliser les bibliothèques de support (si ce n'est pas nécessaire) mais l'api 4.x uniquement par conséquent.

Ma plateforme cible est donc très bien définie comme:> = 4.0 et <= 4.1

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

Similaire à ceci:

entrez la description de l'image ici

Cliquer sur un onglet de la barre d'action change le fragment «externe», et le fragment interne est alors un fragment avec deux fragments imbriqués (1. petit fragment de liste à 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. Le ViewPager fait partie de la bibliothèque de support, il n'y a pas d'alternative native 4.x pour cette classe. Semblent ê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 pour Android 4.2, ce ChildFragmentManagerqui conviendrait bien, mais je cible les versions 4.0 et 4.1, donc cela ne peut pas être utilisé non plus.

ChildFragmentManager n'est disponible qu'en 4.2

Malheureusement, il n'y a guère de bons exemples qui montrent les meilleures pratiques pour l'utilisation de fragments sans la bibliothèque de support, même dans l'ensemble des guides du développeur Android; et surtout rien concernant les fragments imbriqués.

Je me demande donc: n'est-il tout simplement pas possible 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 rencontre actuellement dans le développement est exactement cette déclaration:

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

Remarque: vous ne pouvez pas gonfler une mise en page en un fragment lorsque cette mise en page comprend un fichier <fragment>. Les fragments imbriqués ne sont pris en charge que lorsqu'ils sont ajoutés dynamiquement à un fragment.

Parce que j'ai mis définir les fragments imbriqués dans 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 pour moi-même: même sur 4.1, quand je ne veux même pas cibler la plate-forme 2.x, les fragments imbriqués comme indiqué dans 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à géré).

Mettre à jour:

Une réponse utile est à: Fragment Inside Fragment


22
Vous avez trois options: 1. Ciblez uniquement 4.2 avec des fragments imbriqués natifs. 2. Ciblez 4.x avec des fragments imbriqués de la bibliothèque de support 3. N'utilisez pas de fragments imbriqués pour d'autres scénarios cibles de plate-forme. Cela devrait répondre à votre question. De plus, vous ne pouvez pas utiliser de fragment imbriqué incorporé dans la mise en page xml, tous doivent être ajoutés dans le code. il n'y a guère de bons exemples qui montrent les meilleures pratiques pour l'utilisation de fragments sans la bibliothèque de support - le framework de fragment de support réplique le framework natif, donc tout exemple devrait fonctionner dans les deux sens.
Luksprog

@Luksprog Merci pour vos commentaires. Je préfère votre solution 2, et les framents fonctionnent bien dans la bibliothèque de support, mais les onglets de l'ActionBar ne le font pas - afaik, j'aurais besoin d'utiliser ActionBarSherlock, mais les onglets ne seraient pas intégrés dans l'ActionBar alors mais seulement en dessous (ce qui n'est pas '' t nécessaire pour 4.x). Et ActionBar.TabListener ne prend en charge que les fragments de android.app.Fragment, pas de la bibliothèque de support.
Mathias Conradt

2
Je ne connais pas l'application Contacts sur l'onglet Galaxy, mais gardez à l'esprit que vous pouvez toujours faire face à une implémentation personnalisée du ActionBar(construit en interne par Samsung). Regardez de plus près ActionBarSherlock, il a les onglets dans l'ActionBar s'il y a de la place.
Luksprog

4
@Luksprog Je crois que vous avez déjà fourni la seule réponse à donner, seriez-vous si gentil de la mettre comme une bonne réponse.
Warpzit

1
@Pork Ma principale raison de la question est: existe-t-il des solutions de contournement pour les fragments imbriqués sans avoir à utiliser la bibliothèque de support et tous ses autres éléments d'affichage. Cela signifie que si je passe à la bibliothèque de support, j'utiliserais FragmentActivity au lieu de Fragment. Mais je veux utiliser Fragment, tout ce que je veux, c'est un remplacement pour les fragments imbriqués , mais pas tous les composants v4. Ie via d'autres bibliothèques open source, etc. là-bas. Par exemple, la capture d'écran ci-dessus fonctionne sur 4.0, et je me demande s'ils utilisent l'ABS, SupportLib ou autre chose.
Mathias Conradt

Réponses:


60

Limites

Ainsi, l'imbrication de fragments dans un autre fragment n'est pas possible avec xml quelle que soit la version que FragmentManagervous utilisez.

Vous devez donc ajouter des fragments via du code, cela peut sembler un problème, mais à long terme, vos mises en page sont superflexibles.

Donc imbriquer sans utiliser getChildFragmentManger? L'essence derrière childFragmentManagerest qu'il reporte le chargement jusqu'à ce que la transaction de fragment précédente soit terminée. Et bien sûr, il n'était naturellement pris en charge que dans la version 4.2 ou dans la bibliothèque de support.

Nesting sans ChildManager - Solution

Solution, bien sûr! Je fais cela depuis longtemps maintenant, (depuis que le a ViewPagerété annoncé).

Voir ci-dessous; C'est un Fragmentqui diffère le chargement, donc les Fragments peuvent être chargés à l'intérieur.

C'est assez simple, Handlerc'est une classe vraiment très pratique, en fait, le gestionnaire attend qu'un espace s'exécute sur le thread principal une fois la transaction de fragment actuelle terminée (car les fragments interfèrent avec l'interface utilisateur qu'ils exécutent sur le thread 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 `` meilleure pratique '', mais j'ai des applications en direct utilisant ce hack et je n'ai pas encore de problèmes avec cela.

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


Comment gérez-vous les fragments imbriqués de disposition?
pablisco

La seule façon pour moi de voir cela fonctionner est d'utiliser un CustomLayoutInflater, lorsque vous rencontrez l' fragmentélément, vous surchargez la super implémentation et essayez de l'analyser / gonfler vous-même. Mais ce sera BEAUCOUP d'efforts, bien hors de portée d'une question StackOverflow.
Chris.Jenkins

Salut, quelqu'un peut-il m'aider dans ce problème? Je suis vraiment coincé .. stackoverflow.com/questions/32240138/…
Nicks

2

La meilleure façon de le faire 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 en utilisant l'API 14 actuelle. Ce que j'ai fait, c'est ce qui suit:

1 - regardez la communication entre les 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é parent

classe publique YourActivity extend Activity implémente 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 - Profitez bien, avec cette méthode, vous obtenez la même fonctionnalité fluide qu'avec la fonction getChildFragmentManager () dans un environnement pré-API 17. Comme vous l'avez peut-être remarqué, 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é.


1

J'ai dû faire face à ce problème exact en raison d'une combinaison de NavigationDrawer, TabHost et ViewPager qui présentait des complications avec l'utilisation de la bibliothèque de support à cause de TabHost. Et puis j'ai également dû prendre en charge l'API min de JellyBean 4.1, donc utiliser des fragments imbriqués avec getChildFragmentManager n'était pas une option.

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

TabHost (pour le niveau supérieur)
+ ViewPager (pour un seul des fragments à onglets de niveau supérieur)
= besoin de fragments imbriqués (que JellyBean 4.1 ne supporte pas)

Ma solution était de créer l'illusion de fragments imbriqués sans réellement imbriquer les fragments. J'ai fait cela en faisant en sorte que l'activité principale utilise TabHost ET ViewPager pour gérer deux vues frères 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 tant que je gérais manuellement les pondérations de mise en page pertinentes.

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 / container" sont des frères 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 de toute façon manipulés dans le code pendant l'exécution.


Salut, je suis curieux de savoir pourquoi vous avez choisi d'utiliser TabHost au lieu d'ActionBar avec des onglets? Je suis moi-même passé de TabHost à ActionBar, et mon code est devenu plus propre et plus compact ...
IgorGanapolsky

Autant que je me souvienne, l'un des inconvénients de l'utilisation des onglets dans l'ActionBar est qu'il décide automatiquement de les afficher sous forme de menu déroulant à double flèche (dans le cas d'un petit écran) et ce n'était pas bon pour moi. Mais je ne suis pas sûr à 100%.
WindRider

@Igor, j'ai lu quelque part ici SOque l'utilisation d' ActionBaronglets avec un Navigation Drawern'est pas bon car cela placera automatiquement les onglets sur la vue de votre tiroir. Désolé, je n'ai pas le lien pour sauvegarder cela.
Azurespot

1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Suivez-vous du tout le développement d'Android?
IgorGanapolsky

1
Wow, merci pour le lien @Igor! Je vais vérifier cela à coup sûr. Je suis encore un débutant, j'ai donc un million d'autres choses à apprendre avec Android, mais celui-ci semble être un bijou! Merci encore.
Azurespot du

1

En s'appuyant sur la réponse de @ Chris.Jenkins, c'est la solution qui a bien fonctionné pour moi, pour supprimer le ou les fragments pendant les événements du cycle de vie (qui ont tendance à lancer des exceptions IllegalStateExceptions). Cela utilise une combinaison de l'approche Handler et une vérification Activity.isFinishing () (sinon, il générera une erreur pour «Impossible d'exécuter 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

Bien que l'OP puisse avoir des circonstances particulières qui l'empêchent d'utiliser la bibliothèque de support, la plupart des gens devraient l'utiliser. La documentation Android le recommande et rendra votre application accessible au public le plus large possible.

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

entrez la description de l'image ici


J'étais l'OP. La raison de ne pas utiliser la bibliothèque de support était qu'il s'agissait d'une application interne de l'entreprise, où le matériel utilisé était clairement défini comme> = 4.0 et <= 4.1. Il n'était pas nécessaire d'atteindre un large public, c'était du personnel interne et aucune intention d'utiliser l'application en dehors de l'entreprise. La seule raison de la bibliothèque de support est d'être rétrocompatible - mais on pourrait s'attendre à ce que tout ce que vous pouvez faire avec la bibliothèque de support, vous devriez être capable de le faire "nativement" sans elle. Pourquoi une version supérieure "native" aurait-elle moins de fonctionnalités qu'une bibliothèque de support dont le seul but est simplement d'être compatible vers le bas?
Mathias Conradt le

1
Néanmoins, bien sûr, vous pouvez utiliser la bibliothèque de support et moi aussi. Je ne comprenais tout simplement pas pourquoi Google propose des fonctionnalités UNIQUEMENT dans la bibliothèque d'assistance, mais pas à l'extérieur, ni pourquoi ils l'appelleraient même bibliothèque d'assistance et n'en feraient pas la norme globale, si c'est de toute façon la meilleure pratique. Voici un bon article sur la bibliothèque de support: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@Mathias, bel article.
Suragch le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.