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 returnVal
null
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).
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. –
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). –
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. –