Vollständiges Arbeitsbeispiel des Google Mail-Drei-Fragment-Animationsszenarios?


128

TL; DR: Ich suche nach einem vollständigen Beispiel für das Szenario "Google Mail-Drei-Fragment-Animation". Insbesondere möchten wir mit zwei Fragmenten beginnen:

zwei Fragmente

Bei einem UI-Ereignis (z. B. Tippen auf etwas in Fragment B) möchten wir:

  • Fragment A, um vom Bildschirm nach links zu rutschen
  • Fragment B wird an den linken Bildschirmrand geschoben und verkleinert, um die von Fragment A frei gewordene Stelle einzunehmen
  • Fragment C, um von der rechten Seite des Bildschirms hinein zu gleiten und die von Fragment B frei gewordene Stelle einzunehmen

Und bei einem BACK-Tastendruck möchten wir, dass diese Operationen umgekehrt werden.

Jetzt habe ich viele Teilimplementierungen gesehen; Ich werde vier davon unten überprüfen. Abgesehen davon, dass sie unvollständig sind, haben sie alle ihre Probleme.


@Reto Meier hat diese beliebte Antwort auf dieselbe grundlegende Frage beigesteuert und darauf hingewiesen , dass Sie sie setCustomAnimations()mit a verwenden würden FragmentTransaction. Für ein Szenario mit zwei Fragmenten (z. B. sehen Sie Fragment A zunächst nur und möchten es mithilfe animierter Effekte durch ein neues Fragment B ersetzen) stimme ich voll und ganz zu. Jedoch:

  • Da Sie nur eine "In" - und eine "Out" -Animation angeben können, kann ich nicht sehen, wie Sie mit all den verschiedenen Animationen umgehen würden, die für das Drei-Fragment-Szenario erforderlich sind
  • Der <objectAnimator>in seinem Beispielcode verwendete fest verdrahtete Positionen in Pixel, und dies scheint angesichts unterschiedlicher Bildschirmgrößen unpraktisch zu sein, setCustomAnimations()erfordert jedoch Animationsressourcen, was die Möglichkeit ausschließt, diese Dinge in Java zu definieren
  • Ich bin ratlos , wie das Objekt Animateure für Skala Krawatte in mit Dingen wie android:layout_weightin einem LinearLayoutRaum auf prozentuale Basis für die Zuteilung
  • Ich weiß nicht, wie Fragment C zu Beginn behandelt wird ( GONE? android:layout_weightVon 0? Voranimiert auf eine Skala von 0? Etwas anderes?)

@Roman Nurik weist darauf hin, dass Sie jede Eigenschaft animieren können , auch solche, die Sie selbst definieren. Dies kann helfen, das Problem der fest verdrahteten Positionen zu lösen, und zwar auf Kosten der Erfindung Ihrer eigenen benutzerdefinierten Layout-Manager-Unterklasse. Das hilft einigen, aber ich bin immer noch verblüfft über den Rest der Reto-Lösung.


Der Autor dieses Pastebin-Eintrags zeigt einen verlockenden Pseudocode, der im Grunde besagt, dass sich alle drei Fragmente anfänglich im Container befinden würden, wobei Fragment C zu Beginn über eine hide()Transaktionsoperation ausgeblendet wurde . Wir dann show()C und hide()A, wenn das UI-Ereignis auftritt. Ich sehe jedoch nicht, wie das mit der Tatsache umgeht, dass B die Größe ändert. Es hängt auch von der Tatsache ab, dass Sie anscheinend mehrere Fragmente zum selben Container hinzufügen können, und ich bin mir nicht sicher, ob dies auf lange Sicht ein verlässliches Verhalten ist oder nicht (ganz zu schweigen davon findFragmentById(), dass es kaputt gehen sollte , obwohl ich damit leben kann).


Der Autor dieses Blogposts gibt an, dass Google Mail überhaupt keine verwendet setCustomAnimations(), sondern direkt Objektanimatoren verwendet ("Sie ändern nur den linken Rand der Stammansicht + ändern die Breite der rechten Ansicht"). Dies ist jedoch immer noch eine AFAICT-Lösung mit zwei Fragmenten, und die Implementierung zeigt erneut die Abmessungen der Festdrähte in Pixel.


Ich werde mich weiterhin damit beschäftigen, damit ich eines Tages selbst darauf antworten kann, aber ich hoffe wirklich, dass jemand die Drei-Fragment-Lösung für dieses Animationsszenario ausgearbeitet hat und den Code (oder einen Link dazu) veröffentlichen kann. Animationen in Android bringen mich dazu, mir die Haare auszureißen, und diejenigen unter Ihnen, die mich gesehen haben, wissen, dass dies ein weitgehend erfolgloses Unterfangen ist.


2
Ist es nicht so etwas von Romain Guy? neugierig-creature.org/2011/02/22/…
Fernando Gallego

1
@ ferdy182: Er verwendet keine Fragmente AFAICT. Visuell ist es der richtige Grundeffekt, und ich kann diesen möglicherweise als weiteren Datenpunkt für den Versuch verwenden, eine fragmentbasierte Antwort zu erstellen. Vielen Dank!
CommonsWare

1
Nur ein Gedanke: Die Standard-E-Mail-App hat auf meinem Nexus 7 ein ähnliches (wenn auch nicht vollständig identisches) Verhalten - das sollte Open Source sein.
Christopher Orr

4
Die E-Mail-App verwendet ein benutzerdefiniertes "ThreePaneLayout", das die Position der linken Ansicht und die Breiten / Sichtbarkeiten der beiden anderen Ansichten animiert, von denen jede das entsprechende Fragment enthält.
Christopher Orr

2
hey @CommonsWare Ich werde mich hier nicht um meine ganze Antwort kümmern, aber es gab eine ähnliche Frage wie Ihre, auf die ich eine sehr zufriedenstellende Antwort gegeben habe. Schlagen
siehe

Antworten:


67

Mein Vorschlag wurde bei github hochgeladen (Funktioniert mit allen Android-Versionen, obwohl die Anzeige der Hardwarebeschleunigung für diese Art von Animationen dringend empfohlen wird. Für nicht hardwarebeschleunigte Geräte sollte eine Bitmap-Caching-Implementierung besser passen.)

Das Demo-Video mit der Animation ist hier (langsame Bildrate aufgrund der Bildschirmdarstellung. Die tatsächliche Leistung ist sehr schnell).


Verwendung:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

Erklärung :

Erstellt ein neues benutzerdefiniertes RelativeLayout (ThreeLayout) und 2 benutzerdefinierte Animationen (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutRuft das Gewicht des linken Bereichs als Parameter ab, vorausgesetzt, die andere sichtbare Ansicht hat weight=1.

So new ThreeLayout(context,3)entsteht eine neue Ansicht mit 3 Kindern und der linke Bereich hat 1/3 des Gesamtbildschirms. Die andere Ansicht belegt den gesamten verfügbaren Platz.

Es berechnet die Breite zur Laufzeit. Eine sicherere Implementierung besteht darin, dass die Abmessungen zum ersten Mal in draw () berechnet werden. statt in post ()

Animationen skalieren und übersetzen ändern tatsächlich die Größe und verschieben die Ansicht und nicht pseudo- [skalieren, verschieben]. Beachten Sie, dass fillAfter(true)nirgendwo verwendet wird.

View2 ist rechts von View1

und

View3 ist rechts von View2

Nachdem Sie diese Regeln festgelegt haben, kümmert sich RelativeLayout um alles andere. Animationen verändern die margins(in Bewegung) und [width,height]im Maßstab

Um auf jedes Kind zuzugreifen (damit Sie es mit Ihrem Fragment aufblasen können, können Sie es aufrufen

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Unten werden die 2 Animationen gezeigt


Stufe 1

--- IN Bildschirm ----------! ----- OUT ----

[Ansicht1] [_____ Ansicht2 _____] [_____ Ansicht3_____]

Stufe 2

--OUT -! -------- IN Bildschirm ------

[Ansicht1] [Ansicht2] [_____ Ansicht3_____]


1
Obwohl ich noch keinen Skalierungsanimator implementiert habe, befürchte ich, dass er für feste Farbfelder gut und für "echte" Inhalte weniger gut geeignet ist. Die Ergebnisse der Größenänderung von Fragment B an den Raum von Fragment A sollten nicht anders sein, als wenn Fragment B ursprünglich dort platziert worden wäre. Zum Beispiel skaliert AFAIK, ein Skalierungsanimator, die Schriftgröße (indem alles in der animierten Viewverkleinert wird), aber in Google Mail / E-Mail erhalten wir keine kleineren Schriftarten, sondern nur weniger Wörter.
CommonsWare

1
@CommonsWare scaleAnimation ist nur ein abstrakter Name. Tatsächlich findet keine Skalierung statt. Die Größe der Ansicht wird tatsächlich geändert. Überprüfen Sie die Quelle. LayoutResizer könnte ein besserer Name dafür sein.
Schwachdraht

1
Das ist nicht meine Interpretation der Quelle um den scaleXWert auf View, obwohl ich Dinge möglicherweise falsch interpretiere.
CommonsWare

1
@CommonsWare check this github.com/weakwire/3PaneLayout/blob/master/src/com/example/… out. Der einzige Grund, warum ich Animation erweitere, ist der Zugriff auf die interpolierte Zeit. p.width = (int) width; macht die ganze Magie und es ist nicht scaleX. Es sind die tatsächlichen Ansichtsmaße, die sich während der angegebenen Zeit ändern.
Schwachdraht

2
Ihr Video zeigt eine gute Bildrate, aber die in Ihrem Beispiel verwendeten Ansichten sind sehr einfach. Das Ausführen eines requestLayout () während einer Animation ist sehr kostspielig. Besonders wenn die Kinder komplex sind (zum Beispiel eine ListView). Dies wird wahrscheinlich zu einer abgehackten Animation führen. Die GMail-App ruft requestLayout nicht auf. Stattdessen wird kurz vor Beginn der Animation eine weitere kleinere Ansicht in das mittlere Bedienfeld eingefügt.
Thierry-Dimitri Roy

31

OK, hier ist meine eigene Lösung, abgeleitet von der E-Mail-AOSP-App, gemäß dem Vorschlag von @ Christopher in den Kommentaren der Frage.

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

Die Lösung von @ schwachwire erinnert an meine, obwohl er Animationeher klassische als Animatoren verwendet und RelativeLayoutRegeln verwendet, um die Positionierung durchzusetzen. Vom Standpunkt der Prämie aus wird er wahrscheinlich die Prämie erhalten, es sei denn, jemand anderes mit einer besseren Lösung gibt noch eine Antwort.


Kurz gesagt, das ThreePaneLayoutin diesem Projekt ist eine LinearLayoutUnterklasse, die entworfen wurde, um mit drei Kindern in der Landschaft zu arbeiten. Die Breiten dieser Kinder können im Layout-XML mit den gewünschten Mitteln festgelegt werden. Ich zeige die Verwendung von Gewichten, aber Sie können bestimmte Breiten durch Dimensionsressourcen oder was auch immer festlegen. Das dritte Kind - Fragment C in der Frage - sollte eine Breite von Null haben.

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSelf();
  }

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

Unabhängig davon, wie Sie die Breiten ursprünglich eingerichtet haben, wird zur Laufzeit die Breitenverwaltung übernommen, wenn Sie ThreePaneLayoutzum ersten Mal hideLeft()von der Anzeige der als Fragmente A und B bezeichneten Fragen zu den Fragmenten B und C wechseln. In der Terminologie von ThreePaneLayout- die keine spezifische Bindung an Fragmenten hat - die drei Stücke sind left, middleund right. Zum Zeitpunkt Ihres Anrufs hideLeft()zeichnen wir die Größen leftund middleund Null aller Gewichte auf, die für eine der drei verwendet wurden, sodass wir die Größen vollständig steuern können. Zum Zeitpunkt von setzen hideLeft()wir die Größe von rightauf die ursprüngliche Größe von middle.

Die Animationen sind zweifach:

  • Verwenden Sie a ViewPropertyAnimator, um leftmithilfe einer Hardwareebene eine Übersetzung der drei Widgets links um die Breite von durchzuführen
  • Verwenden Sie eine ObjectAnimatorfür eine benutzerdefinierte Pseudo-Eigenschaft von middleWidth, um die middleBreite von der ursprünglichen Breite auf die ursprüngliche Breite von zu ändernleft

(Es ist möglich, dass es eine bessere Idee ist, ein AnimatorSetund ObjectAnimatorsfür all diese zu verwenden, obwohl dies vorerst funktioniert.)

(Es ist auch möglich, dass der middleWidth ObjectAnimatorWert der Hardwareschicht negiert wird, da dies eine ziemlich kontinuierliche Ungültigmachung erfordert.)

(Es ist definitiv möglich, dass ich immer noch Lücken in meinem Animationsverständnis habe und dass ich Aussagen in Klammern mag)

Der Nettoeffekt besteht darin, dass lefter vom Bildschirm middlerutscht, an die ursprüngliche Position und Größe von gleitet leftund rightdirekt dahinter übersetzt wird middle.

showLeft() kehrt einfach den Prozess um, mit der gleichen Mischung von Animatoren, nur mit umgekehrten Richtungen.

Die Aktivität verwendet a ThreePaneLayout, um ein Paar ListFragmentWidgets und a zu halten Button. Wenn Sie im linken Fragment etwas auswählen, wird das mittlere Fragment hinzugefügt (oder dessen Inhalt aktualisiert). Wenn Sie etwas im mittleren Fragment auswählen, wird die Beschriftung des ButtonPlus festgelegt und hideLeft()auf dem ausgeführt ThreePaneLayout. Wenn Sie die linke Seite ausgeblendet haben, wird BACK ausgeführt showLeft(). Andernfalls beendet BACK die Aktivität. Da dies keine FragmentTransactionsAuswirkungen auf die Animationen hat, können wir diesen "Backstack" nicht selbst verwalten.

Das oben verlinkte Projekt verwendet native Fragmente und das native Animator-Framework. Ich habe eine andere Version desselben Projekts, die den Backport für Android- Supportfragmente und NineOldAndroids für die Animation verwendet:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

Der Backport funktioniert auf einem Kindle Fire der 1. Generation einwandfrei, obwohl die Animation aufgrund der niedrigeren Hardwarespezifikationen und der fehlenden Unterstützung für die Hardwarebeschleunigung etwas ruckelig ist. Beide Implementierungen scheinen auf einem Nexus 7 und anderen Tablets der aktuellen Generation reibungslos zu sein.

Ich bin auf jeden Fall offen für Ideen, wie diese Lösung verbessert werden kann, oder für andere Lösungen, die klare Vorteile gegenüber dem bieten, was ich hier gemacht habe (oder was @weakwire verwendet hat).

Nochmals vielen Dank an alle, die dazu beigetragen haben!


1
Hallo! Vielen Dank für die Lösung, aber ich denke, das Hinzufügen von v.setLayerType () macht die Dinge manchmal etwas langsamer. Entfernen Sie einfach diese Zeilen (und auch den Listener). Dies gilt zumindest für Android 3+ -Geräte, die ich gerade auf dem Galaxy Nexus ausprobiert habe. Ohne diese Zeilen funktioniert es viel besser
Evgeny Nacu,

1
"Beide Implementierungen scheinen auf einem Nexus 7 und anderen Tablets der aktuellen Generation reibungslos zu funktionieren." Ich glaube Ihnen; Aber ich teste Ihre Implementierung mit nativem ObjectAnimator auf einem Nexus 7 und es bleibt etwas zurück. Was wären die wahrscheinlichsten Ursachen? Ich habe bereits hardwareAccelerated: true in AndroidMAnifest. Ich weiß wirklich nicht, was das Problem sein könnte. Ich habe auch DanielGrechs Variante ausprobiert und sie bleibt gleich zurück. Ich kann Unterschiede in der Animation sehen (seine Animation basiert auf Gewichten), aber ich kann nicht sehen, warum sie in beiden Fällen zurückbleiben würde.
Bogdan Zurac

1
@ Andrew: "es bleibt ein bisschen zurück" - ich bin mir nicht sicher, wie Sie das Verb "lag" hier verwenden. Für mich bedeutet "Verzögerung" ein konkretes Vergleichsziel. So kann beispielsweise eine Animation, die an eine Wischbewegung gebunden ist, hinter der Fingerbewegung zurückbleiben. In diesem Fall wird alles durch Tippen ausgelöst, und ich bin mir nicht sicher, was die Animation möglicherweise hinterherhinkt. Ich habe das nicht gesehen, aber ich behaupte nicht, einen starken ästhetischen Sinn zu haben, und was für mich eine akzeptable Animation sein könnte, könnte für Sie inakzeptabel sein.
CommonsWare

2
@ CommonsWare: Ich habe Ihren Code mit 4.2 kompiliert, großartige Arbeit. Als ich auf die Mitte tippte ListViewund die Zurück-Taste schnell drückte und wiederholte, driftete das gesamte Layout nach rechts. Es wurde links eine zusätzliche Leerzeichenspalte angezeigt. Dieses Verhalten wurde eingeführt, weil ich die erste Animation (die durch Tippen auf die Mitte gestartet wurde ListView) nicht abgeschlossen und die Zurück-Taste gedrückt habe. I fixierte es durch das Ersetzen translationXBymit translationXin translateWidgetsVerfahren und translateWidgets(leftWidth, left, middle, right);mit translateWidgets(0, left, middle, right);in - showLeft()Verfahren.
M-WaJeEh

2
Ich habe auch ersetzt requestLayout();mit middle.requestLayout();in - setMiddleWidth(int value);Verfahren. Es hat möglicherweise keinen Einfluss auf die Leistung, da die GPU vermutlich immer noch einen vollständigen Layoutdurchlauf durchführt.
M-WaJeEh

13

Wir haben eine Bibliothek namens PanesLibrary erstellt, die dieses Problem löst. Es ist noch flexibler als das, was bisher angeboten wurde, weil:

  • Jeder Bereich kann dynamisch dimensioniert werden
  • Es erlaubt eine beliebige Anzahl von Fenstern (nicht nur 2 oder 3)
  • Fragmente innerhalb von Fenstern bleiben bei Orientierungsänderungen korrekt erhalten.

Sie können es hier überprüfen: https://github.com/Mapsaurus/Android-PanesLibrary

Hier ist eine Demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Grundsätzlich können Sie auf einfache Weise eine beliebige Anzahl von Fenstern mit dynamischer Größe hinzufügen und Fragmente an diese Fenster anhängen. Ich hoffe, Sie finden es nützlich! :) :)


6

Aufbauend auf einem der Beispiele, auf die Sie verlinkt haben ( http://android.amberfog.com/?p=758 ), wie wäre es mit einer Animation der layout_weightEigenschaft? Auf diese Weise können Sie die Gewichtsänderung der 3 Fragmente zusammen animieren UND erhalten den Bonus, dass sie alle gut zusammengleiten:

Beginnen Sie mit einem einfachen Layout. Da wir animieren werden layout_weight, benötigen wir eine LinearLayoutals Root-Ansicht für die 3 Panels:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

Dann die Demo-Klasse:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

Auch in diesem Beispiel werden FragmentTransactions nicht verwendet, um die Animationen zu erstellen (vielmehr werden die Ansichten animiert, an die die Fragmente angehängt sind). Sie müssten also alle Backstack- / Fragmenttransaktionen selbst ausführen, aber im Vergleich zum Aufwand, die Animationen zum Laufen zu bringen schön, das scheint kein schlechter Kompromiss zu sein :)

Schreckliches Video mit niedriger Auflösung in Aktion: http://youtu.be/Zm517j3bFCo


1
Nun, Google Mail funktioniert auf jeden Fall eine Übersetzung von dem, was ich beschreiben als Fragment A. Ich würde befürchten , dass ihr Gewicht auf 0 reduziert visuelle Artefakte einführen könnten (zB TextViewmit wrap_contentsich ausstrecken vertikal wie die Animation Erlös). Es kann jedoch sein, dass beim Animieren des Gewichts die Größe geändert wird, die ich als Fragment B bezeichnet habe. Danke!
CommonsWare

1
Keine Sorge! Dies ist jedoch ein guter Punkt. Dies würde wahrscheinlich zu visuellen Artefakten führen, je nachdem, welche untergeordneten Ansichten in 'Fragment A' enthalten sind. Hoffen wir, dass jemand einen solideren Weg findet, dies zu tun
DanielGrech

5

Hier werden keine Fragmente verwendet. Es handelt sich um ein benutzerdefiniertes Layout mit 3 untergeordneten Elementen. Wenn Sie auf eine Nachricht klicken, versetzen Sie die 3 Kinder mit offsetLeftAndRight()und einem Animator.

In können JellyBeanSie "Layoutgrenzen anzeigen" in den Einstellungen "Entwickleroptionen" aktivieren. Wenn die Folienanimation abgeschlossen ist, können Sie immer noch sehen, dass das linke Menü noch vorhanden ist, jedoch unter dem mittleren Bereich.

Es ähnelt dem Fly-In-App-Menü von Cyril Mottier , enthält jedoch 3 statt 2 Elemente.

Darüber hinaus ist das ViewPagerdritte Kind ein weiterer Hinweis auf dieses Verhalten: ViewPagerVerwendet normalerweise Fragmente (ich weiß, dass dies nicht erforderlich ist, aber ich habe noch nie eine andere Implementierung gesehen Fragment), und da Sie die drei Kinder nicht Fragmentsin einem anderen verwenden können Fragmentsind wohl keine fragmente ....


Ich habe noch nie "Layoutgrenzen anzeigen" verwendet - das ist sehr hilfreich für Closed-Source-Apps wie diese (danke!). Es scheint, dass das, was ich als Fragment A beschrieben habe, außerhalb des Bildschirms übersetzt wird (Sie können sehen, dass der rechte Rand von rechts nach links verschoben wird), bevor Sie wieder unter das zurückkehren, was ich als Fragment B beschrieben habe. Das fühlt sich wie ein an TranslateAnimation, obwohl ich es bin Sicher gibt es Möglichkeiten, Animatoren zu verwenden, um die gleichen Ziele zu erreichen. Ich muss einige Experimente dazu durchführen. Vielen Dank!
CommonsWare

2

Ich versuche gerade, so etwas zu tun, außer dass die Fragment B-Skala den verfügbaren Platz beansprucht und dass der 3-Bereich gleichzeitig geöffnet werden kann, wenn genügend Platz vorhanden ist. Hier ist meine bisherige Lösung, aber ich bin mir nicht sicher, ob ich dabei bleiben werde. Ich hoffe, jemand wird eine Antwort geben, die den richtigen Weg zeigt.

Anstatt ein LinearLayout zu verwenden und das Gewicht zu animieren, verwende ich ein RelativeLayout und animiere die Ränder. Ich bin mir nicht sicher, ob dies der beste Weg ist, da bei jedem Update ein Aufruf von requestLayout () erforderlich ist. Es ist jedoch auf allen meinen Geräten reibungslos.

Also, ich animiere das Layout, ich verwende keine Fragmenttransaktion. Ich gehe manuell auf die Zurück-Schaltfläche, um Fragment C zu schließen, wenn es geöffnet ist.

FragmentB verwendet layout_toLeftOf / ToRightOf, um es an Fragment A und C auszurichten.

Wenn meine App ein Ereignis auslöst, um Fragment C anzuzeigen, schiebe ich Fragment C ein und schiebe Fragment A gleichzeitig heraus. (2 separate Animation). Umgekehrt, wenn Fragment A geöffnet wird, schließe ich C gleichzeitig.

Im Hochformat oder auf einem kleineren Bildschirm verwende ich ein etwas anderes Layout und schiebe Fragment C über den Bildschirm.

Um den Prozentsatz für die Breite von Fragment A und C zu verwenden, müssten Sie ihn zur Laufzeit berechnen ... (?)

Hier ist das Layout der Aktivität:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

Die Animation zum Ein- oder Ausschieben von FragmentC:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}
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.