2016-09-29 9 views
1

Ich habe eine Aktivität, die mehrere Fragmente enthält. Auf einem Tablet (720dp oder breiter) würde ich eine Liste in einem Fragment auf der linken Seite und dann eines von mehreren Fragmenten in einem Bereich auf der rechten Seite zeigen. Wenn der Benutzer verschiedene Elemente aus der Liste auswählt, ändert sich das Fragment im rechten Fensterbereich. Im Einzelbildmodus würde der Benutzer die Liste der Fragmente sehen, auf eine tippen und dann würde die Liste Fragment für das Detailfragment ausgetauscht werden.Probleme beim Ändern der Ausrichtung von Einzelfenster- zu Doppelfenster-Fragmentlayouts

Hier ist der knifflige Teil: Nehmen Sie ein 7-Zoll-Tablet, so Landschaft ist breit genug für zwei Scheiben, aber Porträt ist breit genug für nur eine. Wechseln Sie zwischen dem Einzelfenstermodus und dem Doppelfenstermodus. Einige Dinge, wie Action Bar Icons und Titel, müssen aktualisiert werden, aber das ist nicht zu kompliziert.

Das Problem kommt in den Fragmenten. Fragmente müssen jongliert werden: Wenn der Benutzer Detailfragment B im Einzelflächenmodus anzeigt und dann in den Doppelscheibenmodus umschaltet, sollte die Liste Fragment links und Fragment B rechts angezeigt werden.

Moving Fragmente herum ist schlimm genug, aber der Backstack ist das eigentliche Problem. Angenommen, ich befinde mich im Doppelscheiben-Modus und lade Fragment C ein. Die Liste Fragment zeigt auf der linken Seite, Detail Fragment C auf der rechten Seite. Dann drehe ich mich in den Single-Panel-Modus. Jetzt sollte ich Detailfragment C sehen, aber wenn ich den Zurück-Knopf drücke, sollte ich zurück zur Liste Fragment gehen. Auf der anderen Seite, wenn ich im Single-Panel-Modus starte und einen Back-Stack aufbaue, dann wechsle in den Double-Panel-Modus, es sollte keinen Back-Stack geben.

Dies scheint eine allgemeine Situation zu sein, in die jeder geraten würde, wenn er versucht, Fragmente zu verwenden und/oder tabletfreundlich zu sein. Sie mussten sich den Herausforderungen stellen, Fragmente zu jonglieren und den Backstack zu manipulieren, wenn sie zwischen Einscheiben- und Doppelscheibenorientierungen wechseln. Bisher habe ich noch keine Antworten oder Beispiele dafür gefunden, nur viele Fragen zu Grundlagen wie "Wie zeige ich zwei Fragmente nebeneinander auf einem Tablet".

Hat sich jemand mit dieser Situation beschäftigt? Was war Ihre Lösung, um dem Benutzer eine einheitliche Erfahrung zu bieten, während er von Fragment zu Fragment wechselte und sich hin und her bewegte?

Antwort

1

Hier ist, was ich getan habe: Im Hochformat habe ich die gleichen Container-Ansichten für die Listenansicht und die Detailansicht gemacht, aber die Listenansicht hatte eine Breite von 0dp. Wenn sich der Benutzer im Querformat- + Detailmodus befand, wenn er vorwärts navigierte, die Ausrichtung änderte und die Zurück-Taste drückte, luden sowohl Listen- als auch Detailfragmente korrekt, aber jetzt würde im Hochformat nur die Detailansicht angezeigt.

Ich wurde nicht sehr anspruchsvoll damit. Wenn Sie im Hochformat navigieren, in der Liste nach Detail gehen und dann in die Landschaft drehen, würde ich Liste und Details anzeigen, aber durch Drücken der Zurück-Taste wird die Liste wieder angezeigt. Meine Philosophie war es, den Backstack so wiederherzustellen, wie er war und bis zu redundanten oder fehlenden Scheiben, wo die Chips hinfallen.

Aber ich denke, dass Sie besser als das tun können. Lassen Sie uns jeden Fall aufnehmen und durcharbeiten.

Anwendungsfall 1: Der Benutzer befindet sich im Querformatmodus. Der Benutzer navigiert zur Liste + Detailfenster und wählt ein Detail aus. Wenn der Benutzer zum Hochformat rotiert, sollte nur das Detailfenster angezeigt werden. Wenn sie jetzt zurück drücken, anstatt der UI vor der Liste + Detail, sollten sie nur die Liste sehen, bevor sie zu ihrem Ausgangspunkt zurückkehren.

Anwendungsfall 2: Der Benutzer befindet sich im Hochformat. Der Benutzer navigiert zum Listenbildschirm und dann zum Detailbildschirm. Wenn der Benutzer in den Querformatmodus wechselt, sollte er die Liste + Details sehen. Wenn sie zurück drücken, sollten sie den Listenbildschirm auf dem hinteren Stapel überspringen.

Ich verbrachte einige gute Zeit mit der GMail App auf meinem Tablet, und es scheint, dies gut zu tun.

Beginnen wir mit dem XML-Layout. Beiden Hoch- und Quer Versionen werden beiden Scheiben haben, aber im Portrait sehen Sie nur ein:

layout/list_detail.xml 

    <LinearLayout 
     android:width="match_parent" 
     android:height="match_parent" 
     orientation="horizontal"> 

     <FrameLayout 
      android:id="+id/list_pane" 
      android:width="match_parent" 
      android:height="match_parent"/> 

     <FrameLayout 
      android:id="+id/detail_pane" 
      android:width="match_parent" 
      android:height="match_parent" 
      android:visibility="gone"/> 

    </LinearLayout> 

layout-landscape/list_detail.xml 

    <LinearLayout 
     android:width="match_parent" 
     android:height="match_parent" 
     orientation="horizontal"> 

     <FrameLayout 
      android:id="+id/list_pane" 
      android:width="240dp" 
      android:height="match_parent"/> 

     <FrameLayout 
      android:id="+id/detail_pane" 
      android:width="0dp" 
      android:weight="1" 
      android:height="match_parent"/> 

    </LinearLayout> 

Die Idee ist, dass Fragmente in beiden Scheiben werden immer geladen werden, sondern im Portrait-Modus nur das eine oder das sehen andere (indem Sie den Bereich wechseln, der sichtbar ist und der Bereich nicht mehr sichtbar ist) und im Querformat sehen Sie beide.

Ich gehe davon aus, dass Sie im Querformat zunächst den Detailbereich mit dem ersten Element in der Liste laden. Sie haben also immer eine Liste und ein Detail, nur dass Sie im Portrait das Detail nicht sehen.

Zunächst wollen wir wissen, ob unsere UI im Listenmodus oder Detail-Modus ist, vor allem, wenn wir von Landschaft zu Portrait gehen:

 private boolean mListMode; 

In onCreate(), werden wir diese initialisieren:

 mListMode = true; 

In onCreateView() wollen wir den gespeicherten Modus Zustand (unter der Annahme eines Fragments hier) gewinnen:

 if (savedInstanceState != null) { 
      mListMode = savedInstanceState.getBoolean("listMode"); 
     } 

     if (getResources().getBoolean(R.bool.is_portrait)) { 
      mListPane.setVisibility(mListMode ? View.VISIBLE : View.GONE); 
      mDetailPane.setVisibility(mListMode ? View.GONE : View.VISIBLE); 
     } 

, die wir in

 @Override 
    public void onSaveInstanceState(Bundle outState) { 
     outState.putBoolean("listMode", mListMode); 
     super.onSaveInstanceState(outState); 
    } 

Nun ist die Listenelement klicken Logik so gehen wird gespeichert haben:

 if (mListMode) { 
      if (getResources().getBoolean(R.bool.is_portrait)) { 
       // TODO animate list -> detail 
       mDetailPane.setVisibility(View.VISIBLE); 
       mListPane.setVisibility(View.GONE); 
      } 
      mListMode = false; // a click puts UI in detail mode, even in landscape 
     } 

Sie müssen sicherstellen, setzen Sie mListMode = false, falls der Benutzer klickt auf etwas in Der Detailbereich, der im Querformat vorwärts navigiert. Wenn er also zum Hochformat rotiert und zurückkehrt, wird der Detailbereich angezeigt.

Okay, jetzt brauchen Sie einen Trick, um die Zurück-Taste für Porträt zu behandeln. Sie könnten in Ihrer Benutzeroberfläche etwas tun (nehmen wir an, es ein Fragment ist):

public boolean onBackPressed() { 

     if (getResources().getBoolean(R.bool.is_portrait) && ! mListMode) { 
      // TODO animate detail -> list 
      mDetailPane.setVisibility(View.GONE); 
      mListPane.setVisibility(View.VISIBLE); 
      mListMode = true; 
      return true; // we handled back button ourselves 
     } 

     return false; // we didn't handle back button 
    } 

Dann in Ihrer Aktivität:

@Override 
    public void onBackPressed() { 

     ListDetailFragment listDetailFragment = (ListDetailFragment) getFragmentManager().findFragmentByTag(LIST_DETAIL_TAG); 

     if (listDetailFragment != null && listDetailFragment.isResumed() && listDetailFragment.onBackPressed()) { 
      return; 
     } 

     super.onBackPressed(); 
    } 

Ich werde die Übergangsanimationen als eine Übung für den Leser überlassen.

+0

Danke für die ausführliche Antwort! –

+1

Ich weiß, dass dies nur für Beispielzwecke ist, aber für die Erbauung dieses Lesens werde ich bemerken: Es ist besser, einen Layout-Ressourcenmodifizierer basierend auf der Größe, wie Layout-w720dp, als Layout-Landschaft zu verwenden. Auf diese Weise werden zwei Fenster auf großen Bildschirmen angezeigt, auch wenn es sich um ein sehr großes Gerät im Hochformat oder um ein Desktop-Fenster handelt. –

+0

Guter Punkt. Meine App verfügt über einen blutigen Code, um zu verhindern, dass Geräte mit kleinen Bildschirmen, z. B. Telefone, in den Querformat-Modus wechseln, mit Ausnahme einer bestimmten Benutzeroberfläche. Schätze, deshalb kam mir das nicht in den Sinn. –

0

@kris larson, Ich akzeptiere Ihre Antwort, um Sie für all Ihre harte Arbeit und detaillierte Erklärungen zu belohnen! Da ich vorwärts pflog und versuchte, etwas selbst herauszufinden, dachte ich, ich würde etwas von dem, was ich versucht hatte, teilen, damit andere von meinen Kopfschmerzen profitieren können. Ich werde sicherlich nicht sagen, das ist der beste Weg!

Ich habe zwei Fragment-Layout-Dateien erstellt. Eines enthielt ein FrameLayout mit der ID fragment_container_master und ein FrameLayout mit der ID fragment_container_detail. Der andere hatte gerade das fragment_container_detail FrameLayout. Ich habe die Doppelscheibe in layout-w720dp und die Einzelscheibe im Layout.

Ich werde feststellen, dass Android Docs es klingen wie die -sw ("kleinste Breite") Modifikator ist vorzuziehen, -w ("Breite").-sw nimmt das kleinere, die Breite oder die Höhe an. Ich fand das nicht relevant für diesen Anwendungsfall: Auf einem großen Bildschirm wollte ich zwei Scheiben, so einfach. Aber vielleicht gibt es einen guten Grund, um -sw zu benutzen.

Mit FragmentTransactions würde ich die Liste der Fragmente zu fragment_container_master und das Standarddetail Fragment zu fragment_container_detail hinzufügen. Wenn der Benutzer auf ein anderes Fragment in der Liste klickte, würde ich das Fragment in fragment_container_detail ersetzen. Leicht genug.

Ich würde die Anwesenheit oder Abwesenheit von fragment_container_master verwenden, um zu wissen, ob ich im Einscheiben- oder Doppelscheiben-Modus arbeitete. Wenn es sich um ein einzelnes Fenster handelt, stelle ich sicher, dass Fragmentübergänge zum Backstack hinzugefügt werden, und stelle sicher, dass die Fragmentliste zunächst in fragment_container_detail geladen wird.

Wie ich bereits erwähnt habe, ist der Wechsel zwischen Single- und Double-Panel (wie beim Drehen eines 7 "-Tabletts) der knifflige Teil. Ich musste die App-Leiste aktualisieren, um zu verhindern, dass Buttons zweimal hinzugefügt werden.

Zuerst I benötigt, um festzustellen, ob I zwischen diesen beiden Modi wurde schaltet. I durch 1, dass in onCreate tun könnte) Erfassen, ob I in Einscheibensicherheitsglas oder Doppelscheiben-Modus 2) detektieren aM Betrieb ob es eine ist Fragment, das mit einer ID von fragment_container_master schwebt Wenn ich im Single-Panel-Modus bin und ein Master-Fragment vorhanden ist, muss es aus dem Doppel-Fenster-Modus übrig bleiben. Wenn ich im Doppel-Fenster-Modus bin, und nicht ein Meister Fragment, es muss nicht initialisiert worden sein, weil ich gerade vom Single-Panel-Modus wechselte.

Wenn ich gerade in den Doppel-Fenster-Modus wechselte, muss ich das Master-Fragment in fragment_container_master laden. Wenn das Detailfragment die Liste Fragment ist, entferne ich es und füge es zu fragment_container_master hinzu. Wenn nicht, zeigten wir bereits ein Detailfragment. Man könnte meinen, ich könnte es einfach dort lassen ... aber da ist das Problem mit dem Backstack. Ich speichere also den Fragmentstatus, platziere ihn und erzeuge ihn mit dem gespeicherten Zustand neu. Dann habe ich eine Double-Panel-Benutzeroberfläche mit nichts auf dem Back-Stack, wie ich will.

Wenn ich gerade in den Single-Panel-Modus gewechselt bin, muss ich entscheiden, was in der Detailansicht angezeigt werden soll. Wenn das Fragment mit der ID fragment_container_detail das Standardfragment ist, kann ich dort die Liste Fragment anzeigen (die vorhandene ersetzen). Wenn es nicht das Standardfragment ist, dann ersetze ich es immer noch mit der Liste Fragment, aber ersetze dann die Liste Fragment durch das Detailfragment, das vorher dort war - stell sicher, dass es zum Backstack hinzugefügt wird.

Klingt ziemlich komplex, oder?

Es wird schlimmer.

Angenommen, der Benutzer ruft den Bildschirm im Doppelfenstermodus auf. Der Benutzer interagiert mit dem Standarddetailfragment, scrollt es möglicherweise oder gibt Daten in ein Feld ein. Dann wird der Bildschirm in den Einzelbildschirmmodus geändert. Der Benutzer möchte offensichtlich das Detailfragment sehen, in dem sie gearbeitet haben. Angenommen, der Benutzer öffnet den Bildschirm und wechselt dann, bevor er etwas anderes tut, in den Einzelflächenmodus. Sie möchten das Standarddetailfragment nicht sehen. Sie wollen das Master-Fragment sehen, das alle anderen Fragmente auflistet. Sie wären ziemlich verwirrt, wenn sie bereits einen Weg gegangen wären, den sie nicht gewählt hatten. Probieren Sie die Google Mail-App aus und Sie werden sehen, wie es genau so funktioniert. Wechseln Sie im Doppelfenstermodus in den Einzelbildschirmmodus (drehen Sie das Tablet) und Sie sehen Ihren Posteingang. Wenn Sie jedoch im Doppel-Fenster-Modus die erste E-Mail scrollen oder mit ihr interagieren und dann in den Single-Panel-Modus wechseln, wird diese E-Mail angezeigt. Ich konnte keinen klugen, einfachen Weg finden, dies zu tun.Es ist mir peinlich, das zu sagen, aber ich hatte gerade eine Menge anderer Zuhörer im Standardfragmentbericht zur Aktivität, als das Fragment interagiert wurde. Also musste ich für jede spezifische Situation kodieren: Wenn der Benutzer auf diese Schaltfläche klickt oder diese ListView berührt oder diese andere Sache tut, dann melde, dass der Benutzer mit diesem Fragment interagiert hat.

Ich entschuldige mich dafür, nicht ins Detail gegangen zu sein oder Code-Beispiele zu erstellen, wollte einfach nur aus dem Kopf für zukünftige Referenz, bevor ich es vergesse.

Verwandte Themen