2013-04-08 9 views
9

Ich habe eine Animation für die Fragments in meiner Anwendung erstellt. Die Animation Animation bewegt zwischen Tabs, zu diesem Zweck muss ich die aktuelle Fragment animieren, um komplett vom Bildschirm zu bewegen, während die Notwendigkeit Fragment von der gegenüberliegenden Seite einschiebt. Ähnlich wie die Animation die ViewPager verwendet.So definieren Sie eine bildschirmgrößenspezifische Animation in XML

Ich muss eine absolute Anfangs- und Endposition des View angeben und da verschiedene Geräte unterschiedliche Dimensionen haben, kann ich kein Animation in XML definieren, das für alle Geräte passt. Bei einem größeren Gerät kann der View Bildschirm möglicherweise nicht komplett wegrutschen und auf einem kleineren Gerät bewegt sich der View zu viel und bewegt sich weiter, wenn er bereits außerhalb des Bildschirms ist.

Also meine Frage ist: Wie kann ich eine Animation in XML definieren, die eine Fragment aus dem Bildschirm schieben und gleichzeitig auf alle Geräte passen?

Meine Animation:

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android">  

    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
    android:interpolator="@android:anim/linear_interpolator" 
    android:propertyName="x" 
    android:valueType="floatType" 
    android:valueTo="720" 
    android:valueFrom="0" 
    android:duration="300"/> 

</set> 

Antwort

17

1) In Bezug auf die Prämie:

Existing answers on SO schlagen die FrameLayout Subklassen ...

Wenn Sie eine ObjectAnimator verwenden möchten, müssen Sie keine andere Wahl, als die View Sie animieren möchten, Unterklasse . Sie müssen die ObjectAnimator mit den notwendigen Getter und Setter-Methoden zur Verfügung stellen, um seine Magie zu tun, da es im Wesentlichen nur diese Getter-und Setter-Methoden aufrufen, um die Animation durchzuführen.

Die Frage, die Sie verknüpfen zu (Animate the transition between fragments) FrameLayout ist Subklassen eine setXFraction() und eine getXFraction() Methode hinzuzufügen. Sie sind so implementiert, dass der x-Wert relativ zur Breite des FrameLayout festgelegt wird. Nur dadurch kann die ObjectAnimator alles andere als zwischen absoluten Werten animieren.

Zusammengefasst, die ObjectAnimator selbst nicht wirklich viel Animation, ruft nur Getter und Setter-Methoden durch Reflexion.

Gibt es wirklich keine Möglichkeit, die tatsächlichen Bildschirmpixelabmessungen (nicht nur dp) in die XML-Datei zu bekommen?

Mit einem ObjectAnimator gibt es keine Möglichkeit, das zu erreichen. ObjectAnimators nur interpolieren von einem Startwert zu und Endwert. Wie ich oben erklärt habe, definiert die Setter-Methode, was tatsächlich passiert.

zum Beispiel eine benutzerdefinierte Funktion aufrufen, die die Breite feinen wäre zurückkehrt, oder definieren eine Konstante, die Code festlegen, Codesatz mit dem Konstante, die die Bildschirmbreite gleich ist, dann, dass konstanten Zugriff von der XML wäre genauso nützlich.

Sie können keinen Wert aus Code in eine XML-Ressource einfügen. Alles, was in Ihren XML-Dateien enthalten ist, wird in Ihr APK kompiliert, wenn Sie es erstellen, und kann zur Laufzeit nicht geändert werden. Und das ist auch eine Antwort auf Ihre andere Frage: Es gibt keine Ressource oder Konstante oder irgendetwas, auf das in XML zugegriffen werden könnte, das die aktuelle Bildschirmgröße enthält. Dynamische Werte wie die Bildschirmgröße des Geräts, auf dem die App installiert ist, können nicht Teil dieser Ressourcen sein, da alles in ihnen beim Erstellen in Ihre App kompiliert wird.


2) Mögliche Lösung: Ansicht Animationen

Eine mögliche Lösung ist Animationen Ansicht anstelle eines ObjectAnimator zu verwenden. Mit Blick auf Animationen können Sie Brüche statt nur absolute Werte angeben:

<set xmlns:android="http://schemas.android.com/apk/res/android"> 
    <translate android:fromYDelta="-100%" android:toYDelta="0%" android:duration="1000"/> 
</set> 

Dieses eine View von -100% zu 0% der Bildschirmhöhe animieren würde. Mit anderen Worten von komplett vom Bildschirm bis zur Position View. Sie können es wie folgt verwenden:

Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down); 
view.startAnimation(animation); 

Ich weiß, dass dies keine Hilfe für Sie sein kann. Es gibt Fälle, in denen man ObjectAnimators verwenden muss und keine Ansichtsanimationen verwenden kann. Aber solange Sie View-Animationen verwenden können, sollte dies Ihr Problem lösen.


3) Best practice

Was wir wirklich hier Adressierung werden sollte, ist, was ich denke, eine falsche Vorstellung von Ihrer Seite ist. Wie Sie bereits bemerkt haben, hat jede Animation, die Sie in XML definieren, absolute Start- und Endwerte. Diese Werte sind statisch und können nach dem Erstellen der App nicht geändert werden.

Wenn man sich anschaut, wie die Ressourcen arbeiten mit den vielen verfügbaren Selektoren und auch mit dp, sp ... dann werden Sie erkennen, dass es ausgelegt ist, eine Sache zu tun:
können Sie zum Beispiel eine Animation definieren, die bewegt a View von 100dp. Dp ist eine Messung der physikalischen Größe, 100dp ist die exakt gleiche physikalische Größe eines beliebigen Bildschirms mit beliebiger Pixeldichte. Mithilfe von Selektoren können Sie diese Animation für Geräte mit kleineren oder größeren Bildschirmen ändern, bei denen die Animation möglicherweise die View zu viel oder zu wenig bewegt. Sie können jedoch nur statische Werte eingeben. Abgesehen von der Arbeit mit Selektoren können Sie die Animation nicht für jedes Gerät anpassen.

Sie sehen also, die Ressourcen sind wirklich nur für alles entwickelt, was statisch und unveränderlich ist. Es funktioniert hervorragend für Dimensionen oder String-Übersetzungen, aber manchmal mit Animationen kann es ein bisschen Schmerz sein.Wie ich oben sagte, bieten nur View-Animationen eine Möglichkeit, die statische Natur zu umgehen, indem sie die Möglichkeit bieten, Brüche anstelle von absoluten Werten zu spezifizieren. Aber im Allgemeinen können Sie nichts Dynamisches in XML definieren.

Um dieses Argument Blick auf Googles ausgezeichnete DevBytes Videos über Animation futher erhärten:

Dann werden Sie feststellen, dass in diesen Beispielen keine einzige Animation in xml definiert ist. Sicher könnte man argumentieren, dass sie sie nicht in xml definieren, weil sie es einfacher haben wollen, den Code zu erklären und zu zeigen, aber das ist meiner Meinung nach ein weiterer Punkt:

Sie können eine Animation nicht statisch definieren Ressourcen, die von einem rein dynamischen Wert abhängen

Da die Bildschirmbreite/-höhe von Gerät zu Gerät unterschiedlich ist, benötigen Sie für jedes Gerät unterschiedliche Animationen. Nur Ansichtsanimationen bieten eine Möglichkeit, Brüche anstelle von absoluten Werten zu definieren. In jedem anderen Fall müssen Sie die Animationen programmgesteuert definieren. Zum Glück ist das nicht schwierig, mit ObjectAnimators:

Animator animator = ObjectAnimator.ofFloat(view, View.X, startValue, endValue); 
animator.start(); 

Dies würde die x-Position der View vom Anfang bis zum Ende Wert in Pixel animiert. Sie können in der Breite der View passieren sie zu animieren, wie Sie wollen:

Animator animator = ObjectAnimator.ofFloat(view, View.X, -view.getWidth(), 0.0f); 
animator.start(); 

Dies würde die View animiert von der linken Seite zu schieben. Es beginnt komplett außerhalb des Bildschirms und stoppt in seiner endgültigen Position. Sie könnten auch dies nur tun:

view.animate().x(targetValue); 

Das die View von seiner aktuellen x-Position auf den Ziel x Wert animieren würde.

Aber bitte missverstehen Sie mich nicht, Sie sollten immer noch versuchen, so viel wie möglich zu definieren - sei es Animationen oder irgendetwas anderes - in den Ressourcen. Hardcoding-Animationen oder andere Werte sollten so weit wie möglich vermieden werden, es sei denn, es ist wie in Ihrem Fall notwendig.


4) Zusammenfassung

Um es zusammenzufassen:

  • Sie können nicht tun, was Sie mit ObjectAnimators tun wollen, ohne die Getter und Setter-Methoden hinzufügen, die Sie brauchen, um die View Sie wollen animieren .
  • Wenn möglich, verwenden Sie view animations. Mit ihnen können Sie Animationen basierend auf Bruchteilen der Breite oder Höhe des View definieren.
  • Wenn das obige nicht funktioniert oder nicht auf Ihre Situation anwendbar ist, dann definieren Sie die Animationen programmatically, dies wird in jedem Fall funktionieren.

Ich hoffe, ich konnte Ihnen helfen und wenn Sie weitere Fragen haben, zögern Sie nicht zu fragen!

+0

Danke für die tolle Antwort. Ich schätze, die große Einschränkung ist, dass die einzige Möglichkeit, einer FragmentTransaction eine Animation hinzuzufügen, in setCustomAnimations (int enter, int exit) besteht. Und diese API scheint eine Animator-Ressourcen-ID und keine Animations-Ressourcen-ID zu erwarten. Das Definieren meines Animators im Code ist kein Problem. Aber kann ich ihm dann eine Ressourcen-ID geben? –

+0

OK, Problem gelöst, ich erstelle meinen Code-Animator in der onCreateAnimator-Methode des Fragments. –

+0

Ich wollte dir jetzt gerade von 'onCreateAnimator() 'erzählen, rate mal, du hast es selbst herausgefunden. Es freut mich, dass ich helfen konnte! –

3

nur verschiedene Ordner erstellen, basierend auf dem Bildschirm Dichten.

Also sagen wir, Ihre XML für Animation befindet sich in einem Ordner namens anim (in res Elternordner).

dann anim-ldpi, anim-mdpi, etc. in res auch zu erstellen. Legen Sie Ihre jeweilige Animation in jeden dieser Ordner, die Bildschirmdichte darstellen und Android wählt die richtige.

+0

ist die Antwort akzeptabel für Sie? Bitte kommentieren oder akzeptieren. – j2emanue

+0

'anim-ldpi',' anim-mdpi' usw. sind Varianten auf dem Bildschirm _density_, nicht Bildschirmgröße. – matiash

+0

Ich denke, diese Antwort vermisst den Punkt der Frage. –

0

Jemand fragte nach meiner Arbeitslösung. Hier ist es. Es ist C# -Code über Mono. Hoffentlich können Java-Autoren herausfinden, was in dieser Sprache sein sollte.

public class MyFragment: Fragment { 

    public int TransitDuration { 
     get { 
     return 500; 
     } 
    } 

    public override Animator OnCreateAnimator(FragmentTransit transit, bool enter, int nextAnim) { 
     switch (transit) { 
     case FragmentTransit.FragmentOpen: 
      { 
      if (enter) { 
       return this.EnterFromLeftAnimator; 
      } else { 
       return this.ExitToRightAnimator; 
      } 
      } 
     case FragmentTransit.FragmentClose: 
      { 
      if (enter) { 
       return this.EnterFromRightAnimator; 
      } else { 
       return this.ExitToLeftAnimator; 
      } 
      } 
     default: 
      Animator r = base.OnCreateAnimator(transit, enter, nextAnim); 
      if (r == null) { 
      if (!this.IsAdded) { 
       this.OnContextHidden(); 
      } 
      } 
      return r; 
     } 
    } 


    public Animator EnterFromLeftAnimator { 
     get { 
     float width = Screen.MainScreen.PixelSize.Width; // this is an object of mine; other code cached the width there long ago. It would actually be better to use the window width. 
     ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", width, 0); 
     animator.SetDuration(this.TransitDuration); 
     Animator r = animator as Animator; 
     return r; 
     } 
    } 
    public Animator ExitToRightAnimator { 
     get { 
     float width = Screen.MainScreen.PixelSize.Width; 
     ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", 0, -width); 
     animator.SetDuration(this.TransitDuration); 
     Animator r = animator as Animator; 
     r.AddListener(new AnimatorEndListenerAdapter(() => this.OnContextHidden())); 
     return r; 
     } 
    } 
    public Animator EnterFromRightAnimator { 
     get { 
     float width = this.ScreenWidth; 
     ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", -width, 0); 
     animator.SetDuration(this.TransitDuration); 
     Animator r = animator as Animator; 
     return r; 
     } 
    } 
    public Animator ExitToLeftAnimator { 
     get { 
     float width = this.ScreenWidth; 
     ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", 0, width); 
     animator.SetDuration(this.TransitDuration); 
     Animator r = animator as Animator; 
     r.AddListener(new AnimatorEndListenerAdapter(() => this.OnContextHidden())); 

     return r; 
     } 
    } 

    public override void OnActivityCreated(Bundle savedInstanceState) { 
     base.OnActivityCreated(savedInstanceState); 
     this.RetainInstance = true; 
    } 

} 
Verwandte Themen