web-dev-qa-db-de.com

Bewährtes Verfahren für verschachtelte Fragmente in Android 4.0, 4.1 (<4.2)) ohne Verwendung der Unterstützungsbibliothek

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

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, ein kleines links und ein Inhaltsfragment rechts) und eine Aktionsleiste mit Registerkarten.

Ähnlich wie dies:

enter image description here

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. breites Inhaltsfragment).

Ich frage mich jetzt, wie es am besten ist, 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 zu ChildFragmentManager gelesen, die gut passen würden, aber ich habe 4.0 und 4.1 im Visier, 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 Unterstützungsbibliothek aufzeigen, selbst in den gesamten Entwicklerhandbüchern für Android; 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 Unterstützungsbibliothek und alles, was dazu gehört, zu verwenden? (Benötigen Sie FragmentActivity anstelle von Fragment usw.?) Oder was wäre die beste Vorgehensweise?


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

Die Unterstützungsbibliothek für Android unterstützt jetzt auch verschachtelte Fragmente, sodass Sie verschachtelte Fragmentdesigns in Android 1.6 und höher implementieren können.

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

Weil ich die verschachtelten Fragmente in XML definiere, verursacht das anscheinend einen Fehler 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 komme ich zu dem Schluss, dass geschachtelte Fragmente, wie im Screenshot gezeigt, auch in Version 4.1 nicht ohne die Unterstützungsbibliothek möglich sind, wenn ich nicht einmal auf die 2.x-Plattform zugreifen möchte.

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

Update:

Eine hilfreiche Antwort lautet: Fragment Inside Fragment

115
Mathias Conradt

Einschränkungen

Das Verschachteln von Fragmenten in ein anderes Fragment ist mit xml also nicht möglich, unabhängig davon, welche Version von FragmentManager Sie verwenden.

Sie müssen also Fragmente per Code hinzufügen. Dies scheint ein Problem zu sein, macht Ihre Layouts jedoch auf lange Sicht überflexibel.

Verschachteln ohne getChildFragmentManger? Das Wesentliche hinter childFragmentManager ist, dass das Laden verschoben wird, bis die vorherige Fragmenttransaktion abgeschlossen ist. Und natürlich wurde es nur natürlich in 4.2 oder der Support-Bibliothek unterstützt.

Schachteln ohne ChildManager - Lösung

Lösung, sicher! Ich mache das schon lange (seit die ViewPager angekündigt wurde).

Siehe unten; Dies ist ein Fragment, das das Laden verzögert, so dass Fragments darin geladen werden kann.

Es ist ziemlich einfach: Handler ist eine wirklich sehr praktische Klasse. Der Handler wartet effektiv darauf, dass ein Leerzeichen im Hauptthread ausgeführt wird, nachdem die aktuelle Fragmenttransaktion abgeschlossen wurde (da Fragmente die Benutzeroberfläche beeinträchtigen, auf der sie ausgeführt werden) Haupt-Bedroung).

// 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" bezeichnen, 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

60
Chris.Jenkins

Der beste Weg, dies in Pre-API 17 zu tun, ist, es überhaupt nicht zu tun. Der Versuch, dieses Verhalten zu implementieren, führt zu Problemen. Das soll jedoch nicht heißen, dass es mit der aktuellen API 14 nicht überzeugend gefälscht werden kann. Ich habe Folgendes getan:

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 verbergen Sie es, indem Sie eine Höhe von 0 eingeben:

<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 extended 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 der 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.

2
Alex

Ich musste mich aufgrund einer Kombination aus NavigationDrawer, TabHost und ViewPager mit genau diesem Problem befassen, was aufgrund von TabHost zu Problemen bei der Verwendung der Support-Bibliothek führte. Außerdem musste ich 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 ...

TabHost (für die oberste Ebene) 
 + ViewPager (nur für eines der mit Registerkarten versehenen Fragmente der obersten Ebene) 
 = Bedarf an verschachtelten Fragmenten (die JellyBean 4.1 nicht unterstützt)

Meine Lösung bestand darin, die Illusion von verschachtelten Fragmenten zu erzeugen, ohne Fragmente tatsächlich zu verschachteln. Zu diesem Zweck verwaltete die Hauptaktivität mit TabHost AND ViewPager zwei gleichgeordnete Ansichten, deren Sichtbarkeit durch Umschalten von layout_weight zwischen 0 und 1 gesteuert 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 manipuliert.

1
Kain Shin

Obwohl das OP unter bestimmten Umständen die Nutzung der Support-Bibliothek verhindern kann, sollten die meisten Benutzer diese verwenden. In der Dokumentation zu Android) wird dies empfohlen, und Ihre App wird einem möglichst breiten Publikum zur Verfügung gestellt.

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

enter image description here

1
Suragch

Aufbauend auf @ Chris.Jenkins Antwort, 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). Hierbei wird eine Kombination aus dem Handler-Ansatz und einer Überprüfung von Activity.isFinishing () verwendet (andernfalls wird der Fehler "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
HelloImKevo