Best Practice für verschachtelte Fragmente in Android 4.0, 4.1 (<4.2) ohne Verwendung der Support-Bibliothek


115

Ich schreibe eine App für 4.0- und 4.1-Tablets, für die ich nicht die Support-Bibliotheken (falls nicht benötigt) verwenden möchte, sondern nur deshalb die 4.x-API.

Meine Zielplattform ist also sehr gut definiert als:> = 4.0 und <= 4.1

Die App verfügt über ein Layout mit mehreren Fenstern (zwei Fragmente, eines links, ein Inhaltsfragment rechts) und eine Aktionsleiste mit Registerkarten.

Ähnlich wie dies:

Geben Sie hier die Bildbeschreibung ein

Durch Klicken auf eine Registerkarte in der Aktionsleiste wird das äußere Fragment geändert, und das innere Fragment ist dann ein Fragment mit zwei verschachtelten Fragmenten (1. kleines Fragment der linken Liste, 2. Fragment mit breitem Inhalt).

Ich frage mich jetzt, was die beste Vorgehensweise ist, um Fragmente und insbesondere verschachtelte Fragmente zu ersetzen. Der ViewPager ist Teil der Support-Bibliothek. Für diese Klasse gibt es keine native 4.x-Alternative. Scheint in meinem Sinne "veraltet" zu sein. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Dann habe ich die Versionshinweise für Android 4.2 gelesen ChildFragmentManager, was gut passt, aber ich ziele auf 4.0 und 4.1 ab, daher kann dies auch nicht verwendet werden.

ChildFragmentManager ist nur in 4.2 verfügbar

Leider gibt es kaum gute Beispiele, die Best Practices für die Verwendung von Fragmenten ohne die Support-Bibliothek zeigen, selbst in den gesamten Android-Entwicklerhandbüchern. und vor allem nichts in Bezug auf verschachtelte Fragmente.

Ich frage mich also: Ist es einfach nicht möglich, 4.1-Apps mit verschachtelten Fragmenten zu schreiben, ohne die Support-Bibliothek und alles, was dazu gehört, zu verwenden? (Müssen Sie FragmentActivity anstelle von Fragment usw. verwenden?) Oder was wäre die beste Vorgehensweise?


Das Problem, das ich derzeit in der Entwicklung habe, ist genau diese Aussage:

Die Android Support Library unterstützt jetzt auch verschachtelte Fragmente, sodass Sie verschachtelte Fragmentdesigns unter Android 1.6 und höher implementieren können.

Hinweis: Sie können ein Layout nicht in ein Fragment aufblasen, wenn dieses Layout a enthält <fragment>. Verschachtelte Fragmente werden nur unterstützt, wenn sie dynamisch zu einem Fragment hinzugefügt werden.

Weil ich die verschachtelten Fragmente in XML definiere, was anscheinend einen Fehler verursacht wie:

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_

Im Moment schließe ich für mich selbst: Selbst in 4.1, wenn ich nicht einmal auf die 2.x-Plattform abzielen möchte, sind verschachtelte Fragmente, wie im Screenshot gezeigt, ohne die Support-Bibliothek nicht möglich.

(Dies ist vielleicht eher ein Wiki-Eintrag als eine Frage, aber vielleicht hat es schon jemand anderes geschafft).

Aktualisieren:

Eine hilfreiche Antwort finden Sie unter: Fragment Inside Fragment


22
Sie haben drei Möglichkeiten: 1. Nur Ziel 4.2 mit nativen verschachtelten Fragmenten. 2. Ziel 4.x mit verschachtelten Fragmenten aus der Unterstützungsbibliothek 3. Verwenden Sie keine verschachtelten Fragmente für andere Plattformzielszenarien. Dies sollte Ihre Frage beantworten. Außerdem können Sie kein verschachteltes Fragment verwenden, das in das XML-Layout eingebettet ist. Alle müssen im Code hinzugefügt werden. Es gibt kaum gute Beispiele, die Best Practices für die Verwendung von Fragmenten ohne die Support-Bibliothek zeigen. Das Support-Fragment-Framework repliziert das native, sodass jedes Beispiel in beide Richtungen funktionieren sollte.
Luksprog

@ Luksprog Danke für deine Kommentare. Ich bevorzuge Ihre Lösung 2, und Framente funktionieren gut in der Support-Bibliothek, aber Registerkarten in der ActionBar funktionieren nicht - afaik, ich müsste ActionBarSherlock verwenden, aber die Registerkarten würden dann nicht in die ActionBar integriert, sondern nur darunter (was nicht der Fall ist). t notwendig für 4.x). Und ActionBar.TabListener unterstützt nur Fragmente aus android.app.Fragment, nicht aus der Support-Bibliothek.
Mathias Conradt

2
Ich bin nicht mit der Kontakte-App auf der Registerkarte "Galaxy" vertraut, aber denken Sie daran, dass Sie immer vor einer benutzerdefinierten Implementierung der ActionBar(von Samsung selbst erstellten) App stehen könnten . Schauen Sie sich ActionBarSherlock genauer an. Es enthält die Registerkarten in der ActionBar, wenn Platz vorhanden ist.
Luksprog

4
@ Luksprog Ich glaube, Sie haben bereits die einzige Antwort geliefert, die es zu geben gibt. Würden Sie so freundlich sein, sie als richtige Antwort einzutragen?
Warpzit

1
@Pork Mein Hauptgrund für die Frage ist: Gibt es Problemumgehungen für verschachtelte Fragmente, ohne die Support-Bibliothek und alle anderen Ansichtselemente verwenden zu müssen? Das heißt, wenn ich zur Unterstützungsbibliothek wechsle, würde ich FragmentActivity anstelle von Fragment verwenden. Aber ich möchte Fragment verwenden. Ich möchte nur einen Ersatz für verschachtelte Fragmente , aber nicht alle v4-Komponenten. Dh über andere Open-Source-Bibliotheken usw. da draußen. Der obige Screenshot läuft beispielsweise unter 4.0, und ich frage mich, ob sie ABS, SupportLib oder etwas anderes verwenden.
Mathias Conradt

Antworten:


60

Einschränkungen

Das Verschachteln von Fragmenten in einem anderen Fragment ist mit xml daher nicht möglich, unabhängig davon, welche Version FragmentManagerSie verwenden.

Sie müssen also Fragmente per Code hinzufügen, dies scheint ein Problem zu sein, macht Ihre Layouts aber auf lange Sicht superflexibel.

Also nisten ohne zu benutzen getChildFragmentManger? Das Wesentliche dahinter childFragmentManagerist, dass das Laden verschoben wird, bis die vorherige Fragmenttransaktion abgeschlossen ist. Und natürlich wurde es natürlich nur in 4.2 oder der Support-Bibliothek unterstützt.

Verschachteln ohne ChildManager - Lösung

Lösung, sicher! Ich mache das schon lange (seit dem ViewPagerangekündigt wurde).

Siehe unten; Dies ist eine FragmentOption, die das Laden verzögert, sodass Fragments darin geladen werden kann.

Es ist ziemlich einfach, das Handlerist eine wirklich sehr praktische Klasse. Der Handler wartet effektiv darauf, dass ein Leerzeichen im Hauptthread ausgeführt wird, nachdem die aktuelle Fragmenttransaktion festgeschrieben wurde (da Fragmente die Benutzeroberfläche stören, die sie im Hauptthread ausführen).

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

Ich würde es nicht als "Best Practice" betrachten, aber ich habe Live-Apps, die diesen Hack verwenden, und ich habe noch keine Probleme damit.

Ich verwende diese Methode auch zum Einbetten von Ansichtspagern - https://gist.github.com/chrisjenx/3405429


Wie gehen Sie mit verschachtelten Layoutfragmenten um?
Pablisco

Die einzige Möglichkeit, dies zu sehen, ist die Verwendung eines CustomLayoutInflater. Wenn Sie auf das fragmentElement stoßen, überschreiben Sie die Super-Implementierung und versuchen, sie selbst zu analysieren / aufzublasen. Aber das wird eine Menge Aufwand sein, weit außerhalb des Rahmens einer StackOverflow-Frage.
Chris.Jenkins

Hallo, kann mir jemand in dieser Ausgabe helfen? Ich stecke wirklich fest .. stackoverflow.com/questions/32240138/…
Nicks

2

Der beste Weg, dies in Pre-API 17 zu tun, besteht darin, es überhaupt nicht zu tun. Der Versuch, dieses Verhalten zu implementieren, führt zu Problemen. Dies bedeutet jedoch nicht, dass es mit der aktuellen API 14 nicht überzeugend gefälscht werden kann. Was ich getan habe, war Folgendes:

1 - Sehen Sie sich die Kommunikation zwischen Fragmenten an http://developer.android.com/training/basics/fragments/communicating.html

2 - Verschieben Sie Ihr Layout-XML-FrameLayout von Ihrem vorhandenen Fragment in das Aktivitätslayout und blenden Sie es aus, indem Sie eine Höhe von 0 angeben:

<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 - Implementieren Sie die Schnittstelle im übergeordneten Fragment

    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 - Implementieren Sie die Schnittstelle in der übergeordneten Aktivität

öffentliche Klasse YourActivity erweitert Activity implementiert 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 - Genießen Sie, mit dieser Methode erhalten Sie die gleiche flüssige Funktionalität wie mit der Funktion getChildFragmentManager () in einer Umgebung vor API 17. Wie Sie vielleicht bemerkt haben, ist das untergeordnete Fragment nicht mehr wirklich ein Kind des übergeordneten Fragments, sondern jetzt ein Kind der Aktivität. Dies kann wirklich nicht vermieden werden.


1

Ich musste mich genau mit diesem Problem befassen, da NavigationDrawer, TabHost und ViewPager kombiniert wurden und aufgrund von TabHost Probleme bei der Verwendung der Support-Bibliothek auftraten. Und dann musste ich auch die min-API von JellyBean 4.1 unterstützen, sodass die Verwendung verschachtelter Fragmente mit getChildFragmentManager keine Option war.

So kann mein Problem destilliert werden, um ...

TabHost (für die oberste Ebene)
+ ViewPager (nur für eines der Fragmente mit Registerkarten der obersten Ebene)
= Notwendigkeit für verschachtelte Fragmente (die JellyBean 4.1 nicht unterstützt)

Meine Lösung bestand darin, die Illusion verschachtelter Fragmente zu erzeugen, ohne tatsächlich Fragmente zu verschachteln. Ich habe dies getan, indem die Hauptaktivität TabHost AND ViewPager verwendet hat, um zwei Geschwisteransichten zu verwalten, deren Sichtbarkeit durch Umschalten von layout_weight zwischen 0 und 1 verwaltet wird.

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

Dies ermöglichte es meinem gefälschten "verschachtelten Fragment" effektiv, als unabhängige Ansicht zu arbeiten, solange ich die relevanten Layoutgewichte manuell verwaltete.

Hier ist meine 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>

Beachten Sie, dass "@ + id / pager" und "@ + id / container" Geschwister mit 'android: layout_weight = "0.5"' und 'android: layout_height = "0dp"' sind. Dies ist so, dass ich es in der Vorschau für jede Bildschirmgröße sehen kann. Ihre Gewichte werden ohnehin zur Laufzeit im Code bearbeitet.


Hallo, ich bin gespannt, warum Sie TabHost anstelle von ActionBar mit Tabs verwendet haben. Ich selbst wechselte von TabHost zu ActionBar und mein Code wurde sauberer und kompakter ...
IgorGanapolsky

Soweit ich mich erinnere, besteht ein Nachteil der Verwendung von Registerkarten in der ActionBar darin, dass automatisch entschieden wird, sie als Spinner-Dropdown-Menü anzuzeigen (bei einem kleinen Bildschirm), und das war nicht gut für mich. Aber ich bin nicht 100% sicher.
WindRider

@Igor, ich habe hier irgendwo gelesen, SOdass die Verwendung von ActionBarRegisterkarten mit einem Navigation Drawernicht gut ist, da die Registerkarten automatisch über der Ansicht Ihrer Schublade platziert werden. Entschuldigung, ich habe keinen Link, um dies zu sichern.
Azurespot

1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Verfolgen Sie die Entwicklung von Android überhaupt?
IgorGanapolsky

1
Wow, danke für den Link @Igor! Ich werde das auf jeden Fall überprüfen. Ich bin noch ein Anfänger, also habe ich eine Million anderer Dinge mit Android zu lernen, aber dieses scheint ein Juwel zu sein! Danke noch einmal.
Azurespot

1

Aufbauend auf der Antwort von @ Chris.Jenkins ist dies die Lösung, die für mich gut funktioniert hat, um Fragmente während Lebenszyklusereignissen zu entfernen (die dazu neigen, IllegalStateExceptions auszulösen). Dies verwendet eine Kombination aus dem Handler-Ansatz und einer Activity.isFinishing () -Prüfung (andernfalls wird ein Fehler für "Kann diese Aktion nach onSaveInstanceState nicht ausführen) ausgegeben.

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

Verwendung:

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

1

Obwohl das OP besondere Umstände hat, die ihn daran hindern, die Support-Bibliothek zu verwenden, sollten die meisten Benutzer sie verwenden. In der Android-Dokumentation wird dies empfohlen, und Ihre App wird einem möglichst breiten Publikum zugänglich gemacht.

In meiner ausführlicheren Antwort hier habe ich ein Beispiel gemacht, das zeigt, wie verschachtelte Fragmente mit der Unterstützungsbibliothek verwendet werden.

Geben Sie hier die Bildbeschreibung ein


Ich war der OP. Der Grund, die Support-Bibliothek nicht zu verwenden, war, dass es sich um eine unternehmensinterne App handelte, bei der die verwendete Hardware eindeutig als> = 4.0 und <= 4.1 definiert war. Es war nicht erforderlich, ein breites Publikum zu erreichen, es waren interne Mitarbeiter und keine Absicht, die App außerhalb des Unternehmens zu verwenden. Der einzige Grund für die Support-Bibliothek besteht darin, abwärtskompatibel zu sein - aber man würde erwarten, dass alles, was Sie mit der Support-Bibliothek tun können, "nativ" ohne sie erreicht werden kann. Warum sollte eine "native" höhere Version weniger Funktionen haben als eine Unterstützungsbibliothek, deren einziger Zweck darin besteht, nur abwärtskompatibel zu sein?
Mathias Conradt

1
Trotzdem könnten Sie natürlich die Support-Bibliothek nutzen und ich auch. Ich habe nur nicht verstanden, warum Google Funktionen NUR in der Support-Bibliothek anbietet, aber nicht außerhalb, oder warum sie es sogar Support-Bibliothek nennen und es dann nicht zum Gesamtstandard machen, wenn es sowieso die beste Vorgehensweise ist. Hier ist ein guter Artikel über die Support-Bibliothek: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@ Mathias, schöner Artikel.
Suragch
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.