2017-04-27 2 views
4

Drawable Ich schrieb einen animierten Vektor mit Path Morphing (die nur auf API 21 und höher verfügbar ist). Ich habe eine Fallback-Animation mit einer einfachen Drehung für API unter 21. Ich verwende die animierte Vektor-Zeichnungs-Support-Bibliothek (com.android.support:animated-vector-drawable:25.3.1).Animierter Vektor Mit compat-Bibliothek auch auf API 22-Gerät

Hier ist, wie ich die Animation zu starten:

mBinding.switchIcon.setImageResource(R.drawable.ic_animated_vertical_arrow_down_to_up_32dp); 

final Drawable animation = mBinding.switchIcon.getDrawable(); 
if (animation instanceof Animatable) { 
    ((Animatable) animation).start(); 
}

Dies funktioniert gut auf API 19 und 24, aber funktioniert nicht auf API 22 noch 23 (Ich habe keine API 21 Gerät zu testen).

Die API 19 Fall ist logisch: Die Animation ist einfach, wird von der Support-Bibliothek perfekt behandelt, es funktioniert. Groß.

Ich erwartete alle API 21 und höher Geräte die integrierte Vector Drawable-Implementierung auswählen. Beim Debuggen kann ich jedoch sehen, dass tatsächlich eine Instanz von AnimatedVectorDrawableCompat ist: Daher unterstützt es kein Pfadmorphing und die Animation funktioniert nicht.

Warum funktioniert es auf API 24? Nun, animation ist eine Instanz von AnimatedVectorDrawable. Daher funktioniert Pfad-Morphing gut.

So ist meine Frage: warum nicht API 21-23 Geräte im internen Implementierung abholen und verlassen sich auf der Support-Bibliothek, während ein API-24-Gerät nicht es abholen?


  • Als Randbemerkung, das Gerät zwingt die Umsetzung in integrierten zu holen tut funktioniert offensichtlich:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
        AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) getDrawable(R.drawable.ic_animated_vertical_arrow_down_to_up_32dp); 
        mBinding.switchIcon.setImageDrawable(drawable); 
    } else { 
        mBinding.switchIcon.setImageResource(R.drawable.ic_animated_vertical_arrow_down_to_up_32dp); 
    } 
    
    final Drawable animation = mBinding.switchIcon.getDrawable(); 
    if (animation instanceof Animatable) { 
        ((Animatable) animation).start(); 
    }
  • Ich fand auch diese (wahrscheinlich) in Verbindung stehende Ausgabe auf der Google-Bug -tracker: https://issuetracker.google.com/issues/37116940

  • Mit einem Debugger kann ich bestätigen, dass auf API 22 (und wahrscheinlich 23), die Support-Bibliotheken tatsächlich die Arbeit an die SD delegieren K's AnimatorSet. Ich verstehe die Verhaltensänderung wirklich nicht.


Worüber folgt

Dies sind einige Notizen, die ich dachte, könnte interessant sein, zu teilen, die ich in der technischen Erklärung dieses Problem während der Untersuchung nahm. Die interessanten, weniger technischen Bits sind in der akzeptierten Antwort zusammengefasst.


Hier sind die AVD ich verwende, als Referenz:

<animated-vector 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:aapt="http://schemas.android.com/aapt"> 
    <aapt:attr name="android:drawable"> 
     <vector 
      xmlns:android="http://schemas.android.com/apk/res/android" 
      android:width="32dp" 
      android:height="32dp" 
      android:viewportWidth="24" 
      android:viewportHeight="24" 
      android:alpha="1"> 
      <group android:name="group"> 
       <path 
        android:name="path" 
        android:pathData="@string/vertical_arrow_up_path" 
        android:strokeColor="#000000" 
        android:strokeWidth="2" 
        android:strokeLineCap="square"/> 
      </group> 
     </vector> 
    </aapt:attr> 
    <target android:name="path"> 
     <aapt:attr name="android:animation"> 
      <objectAnimator 
       xmlns:android="http://schemas.android.com/apk/res/android" 
       android:name="path" 
       android:propertyName="pathData" 
       android:duration="300" 
       android:valueFrom="@string/vertical_arrow_up_path" 
       android:valueTo="@string/vertical_arrow_down_path" 
       android:valueType="pathType" 
       android:interpolator="@android:anim/accelerate_decelerate_interpolator"/> 
     </aapt:attr> 
    </target> 
</animated-vector> 

Und beiden Pfade Ressourcen:

<string name="vertical_arrow_up_path" translatable="false">M 7.41 10 L 12 14.585 L 16.59 10</string> 
<string name="vertical_arrow_down_path" translatable="false">M 7.41 14.585 L 12 10 L 16.59 14.585</string>

Auf einer Einrichtung 22 API, sowohl die integrierten -in und die Support-Version (25.3.1) scheint das gleiche Animator von meinem AVD oben aufzublasen, wenn auch mit einer anderen Hierarchie.

Mit Unterstützung Version (25.3.1) hat die AnimatorSet nur ein Knoten: ein AnimatorSet selbst eine einzelne Animation enthält, scheinbar die ObjectAnimator in dem XML-AVD beschrieben entsprechen. Sein referent ist auf die VectorDrawableCompat gesetzt, der Name der Eigenschaft ist zu Recht pathData, und die Werteliste enthält eine einzige PropertyValuesHolder mit zwei Keyframes, passend zu meinen Start- und Endpfade. Ergebnis: funktioniert nicht.

Mit der build-in-Version (SDK 22), dann ist es nicht genau die gleiche (aber die AnimatorSet ist nicht genau an der gleichen Stelle, so ...): in den AnimatedVectorDrawableState, die mAnimators Liste 1 Element hat, das ist direkt die ObjectAnimator (mit den gleichen Werten wie bei der Support-Version). Ergebnis: funktioniert.

Der einzige relevante Unterschied, den ich sehen kann, ist die ValueAnimator in der PropertyValuesHolder. Da es einen Verweis auf die Zeichnungsdatei hat, denke ich, es kann einige typecheck ignorieren die Unterstützung Bibliothek Version der VectorDrawable Klasse. Aber das ist reine Spekulation an diesem Punkt. Ich werde graben halten ...


ich es endlich (und @ LewisMcGeary Antwort akzeptiert, da ich nicht in dieser Frage nicht erwähnt, dass ich für die technischen Bits hinter dem Problem suchen). Hier passiert was passiert. Wie bereits erwähnt, übernimmt die Support-Bibliothek auf den APIs 21-23 die Implementierung des SDK, um Fehler in diesen Implementierungen zu vermeiden. So verwenden wir AnimatedVectorDrawableCompat und andere [whatever]Compat Klassen. Sobald der Vektor selbst geladen ist, ist die Animation an der Reihe.

Die Animation ist an die SDKs ObjectAnimator delegiert, unabhängig von der API-Ebene, auf der wir sind (mindestens ab 21+, aber ich denke, es ist das Gleiche unter 19 und unten). Um primitive Typen zu animieren, verfügt die ObjectAnimator über eine interne Zuordnung von Funktionen, die aufgerufen werden, um die Werte zu ändern. Bei komplexen Typen wird jedoch eine bestimmte Methodensignatur verwendet, die auf dem animierten Objekt enthält. Hier ist das Verfahren Mapping Werttyp Methode entsprechend zu nennen, von PropertyValuesHolder (SDK, API 22):

private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) { 
     // TODO: faster implementation... 
     Method returnVal = null; 
     String methodName = getMethodName(prefix, mPropertyName); 
     Class args[] = null; 
     if (valueType == null) { 
      try { 
       returnVal = targetClass.getMethod(methodName, args); 
      } catch (NoSuchMethodException e) { 
       // Swallow the error, log it later 
      } 
     } else { 
      args = new Class[1]; 
      Class typeVariants[]; 
      if (valueType.equals(Float.class)) { 
       typeVariants = FLOAT_VARIANTS; 
      } else if (valueType.equals(Integer.class)) { 
       typeVariants = INTEGER_VARIANTS; 
      } else if (valueType.equals(Double.class)) { 
       typeVariants = DOUBLE_VARIANTS; 
      } else { 
       typeVariants = new Class[1]; 
       typeVariants[0] = valueType; 
      } 
      for (Class typeVariant : typeVariants) { 
       args[0] = typeVariant; 
       try { 
        returnVal = targetClass.getMethod(methodName, args); 
        if (mConverter == null) { 
         // change the value type to suit 
         mValueType = typeVariant; 
        } 
        return returnVal; 
       } catch (NoSuchMethodException e) { 
        // Swallow the error and keep trying other variants 
       } 
      } 
      // If we got here, then no appropriate function was found 
     } 

     if (returnVal == null) { 
      Log.w("PropertyValuesHolder", "Method " + 
        getMethodName(prefix, mPropertyName) + "() with type " + valueType + 
        " not found on target class " + targetClass); 
     } 

     return returnVal; 
    } 

Der interessante Teil ist die Schleife for jeden potentiellen typeVariants unsere Zielklasse anzupassen versucht. In diesem speziellen Fall enthält typeVariants nur einen Class Gegenstand: android.util.PathParser$PathDataNode. Die Klasse, für die wir eine Methode aufrufen möchten (targetClass), ist unsere Compat-Klasse: android.support.graphics.drawable.VectorDrawableCompat$VFullPath. Und die Methode, die wir suchen (methodName) ist .

Leider VectorDrawableCompat$VFullPath.setPathData ‚s Signatur nicht übereinstimmt: public void android.support.graphics.drawable.VectorDrawableCompat$VPath.setPathData(android.support.graphics.drawable.PathParser$PathDataNode[])

Da wir nur ein Element in den typeVariants Array haben, endet returnValnull zu sein, und am Ende die ObjectAnimator hat absolut keine Möglichkeit, zu wissen, wie um die Pfaddaten unserer VectorDrawableCompat zu aktualisieren.

Also woher kommt der typeVariants Inhalt? Die android.util.PathParser$PathDataNode statt der Unterstützung? Es ist wegen der Art, wie die Animation aufgeblasen ist.AnimatedVectorDrawableCompat, wie wir gesehen haben, delegiert einen Großteil der Arbeit an das SDK, weshalb einige Dinge nicht auf APIs 19 und darunter funktionieren. Wenn die target Knoten seiner XML-Lesen wird die Animator vom SDK aufgeblasen:

Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);

Die AnimatorInflater aus dem SDK kommt, und daher wird eine android.util.PathParser$PathDataNode anstelle eines android.support.graphics.drawable.PathParser$PathDataNode aufpumpen. Ich denke, die einzige mögliche Lösung hierfür wäre für Google die AnimatorInflater in den Support-Bibliotheken zu integrieren ...


So sind wir in einer harten Position hier. Google gibt zu, dass die VectorDrawable Implementierung von SDKs 21-23 Bugs enthält (ich bemerkte einige Zeichnungsprobleme in API 22 auf einigen SVGs), aber wir können auch nicht alles aus der Support-Bibliothek verwenden. Also, denken Sie daran, dass auf 19 (oder unten) zu testen, 21, 22, 23 und 24 (oder höher) nur verpflichtend, wenn es um VectorDrawable s kommt ...


Edit: ab heute (09/06/2017), Google veröffentlichte Support-Bibliotheken 25.4, die Back-Ports Path-Morphing auf API 14+ zurück. Ich denke, dieses Problem wird jetzt automatisch gelöst (ich habe es noch nicht getestet).

Antwort

1

AnimatedVectorDrawableCompat prüft die Version intern und delegiert nur dann an die Systemimplementierung, wenn die Version API 24 oder höher ist (zum Zeitpunkt des Schreibens).

Für die Argumentation scheint es so zu sein, wie in dem Problem erwähnt, mit dem Sie verbunden sind, um Probleme mit der eingebauten Implementierung für frühere APIs zu vermeiden.

Für die neueste, here's the git commit, die sich auf this issue in the issue tracker über Rendering-Probleme bezieht.

Leider bedeutet das, dass das Reparieren einiger Dinge auch andere Features entfernt (zB Pfadmorphing). Ich denke, die Art von Ansatz, die Sie in der Frage verwenden, ist wirklich die einzige Option, um dies zu umgehen.

+1

Nun, das dachte man zuerst, aber selbst auf API 22 wird der 'AnimatedVectorDrawable' Delegat der Plattform verwendet. Ich bemerkte einen Unterschied in den Animationen, die an das AnimatorSet zwischen 22 und 24 gedacht wurden. Ich werde heute Abend mehr hineingraben. –

+0

Ich machte weitere Recherchen und aktualisierte die Frage mit weiteren Details. Soweit ich sehen kann, lesen auf einem API 22-Gerät sowohl compat 25 als auch SDK-Implementierungen den gleichen "ObjectAnimator" am Ende - der eine zielt auf einen "VectorDrawable" ab, der andere auf einen "VectorDrawableCompat". Ich nehme an, dass der 'ObjectAnimator' der Plattform eine Typprüfung durchführt und die Compat-Version nicht erkennt (ich habe noch keinen Beweis dafür). –

+0

Also habe ich deine Antwort angenommen, als du tatsächlich die Frage beantwortet hast, die ich gestellt habe (auch wenn es nicht das war, was ich wirklich * wollte, was die technische Erklärung war, aber ich denke, du konntest meine Gedanken nicht lesen :-)). Ich habe den technischen Grund für das Problem gefunden, ich füge es der Frage hinzu. –

Verwandte Themen