【发布时间】:2017-04-27 09:57:54
【问题描述】:
我使用路径变形(仅在 API 21 及更高版本上可用)编写了一个可绘制的动画矢量。我有一个使用简单旋转 API 低于 21 的后备动画。我正在使用动画矢量可绘制支持库 (com.android.support:animated-vector-drawable:25.3.1)。
这是我开始动画的方式:
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();
}
这在 API 19 和 24 上运行良好,但在 API 22 和 23 上不起作用(我没有要测试的 API 21 设备)。
API 19 案例是合乎逻辑的:动画很简单,由支持库完美处理,它可以工作。太好了。
我希望任何 API 21 及更高版本的设备都选择内置矢量可绘制实现。但是在调试的时候,我发现animation实际上是AnimatedVectorDrawableCompat的一个实例:因此,它不支持路径变形,动画也不起作用。
那么为什么它适用于 API 24?好吧,animation 是AnimatedVectorDrawable 的一个实例。因此,路径变形可以正常工作。
所以我的问题是:为什么 API 21-23 设备不采用内置实现并依赖支持库,而 API 24 设备可以 em> 捡起来?
-
作为旁注,强制设备选择内置实现显然有效:
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(); } 我还在 Google 错误跟踪器上发现了这个(可能)相关问题:https://issuetracker.google.com/issues/37116940
使用调试器,我可以确认在 API 22(可能还有 23)上,支持库确实将工作委托给 SDK 的
AnimatorSet。我真的不明白行为变化。
关于以下内容
这些是我认为可能很有趣的一些笔记,我在调查此问题的技术解释时记录了这些笔记。已接受的答案中总结了有趣且技术含量较低的部分。
这是我正在使用的 AVD,供参考:
<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>
以及两个路径资源:
<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>
在 API 22 设备上,内置版本和支持版本 (25.3.1) 似乎都从我上面的 AVD 中扩充了相同的 Animator,尽管层次结构不同。
在支持版本 (25.3.1) 中,AnimatorSet 只有一个节点:AnimatorSet 自身包含一个动画,看起来与 AVD 的 XML 中描述的 ObjectAnimator 匹配。它的referent 设置为VectorDrawableCompat,属性名称是正确的pathData,值列表包含一个带有两个关键帧的PropertyValuesHolder,匹配我的开始和结束路径。结果:不起作用。
对于内置版本(SDK 22),它并不完全相同(但AnimatorSet 并不完全相同,所以……):在AnimatedVectorDrawableState 中,mAnimators 列表有1 个元素,直接是ObjectAnimator(与支持版本具有相同的值)。结果:有效。
我能看到的唯一相关区别是PropertyValuesHolder 中的ValueAnimator。由于它对可绘制对象有一些参考,我猜它可能有一些类型检查,忽略了VectorDrawable 类的支持库版本。但这纯粹是猜测。我会继续挖掘……
我终于明白了(并接受了@LewisMcGeary 的回答,因为我在这个问题中没有提到我正在寻找问题背后的技术细节)。这就是发生的事情。如前所述,在 API 21-23 上,支持库正在接管 SDK 的实现,以避免上述实现中的错误。所以我们使用AnimatedVectorDrawableCompat 和其他[whatever]Compat 类。一旦加载了矢量本身,就轮到动画了。
动画被委派给 SDK 的ObjectAnimator,无论我们处于何种 API 级别(至少在 21+,但我想在 19 及以下也是一样的)。要为原始类型设置动画,ObjectAnimator 有一个内部函数映射,可以调用以更改值。然而,在复杂类型上,它依赖于特定的方法签名,必须出现在动画对象上。这是从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;
}
有趣的部分是for 循环试图将任何潜在的typeVariants 匹配到我们的目标类。在这种特定情况下,typeVariants 仅包含一个 Class 对象:android.util.PathParser$PathDataNode。我们试图在 (targetClass) 上调用方法的类是我们的 Compat 类:android.support.graphics.drawable.VectorDrawableCompat$VFullPath。我们正在寻找的方法(methodName)是setPathData。
很遗憾,VectorDrawableCompat$VFullPath.setPathData 的签名不匹配:public void android.support.graphics.drawable.VectorDrawableCompat$VPath.setPathData(android.support.graphics.drawable.PathParser$PathDataNode[])
由于我们在typeVariants数组中只有一项,所以returnVal以null结尾,最后ObjectAnimator完全不知道如何更新我们@987654368的路径数据@。
那么typeVariants 内容从何而来? android.util.PathParser$PathDataNode 而不是支持者?这是因为动画的膨胀方式。正如我们所见,AnimatedVectorDrawableCompat 将大部分工作委派给 SDK,这就是为什么有些事情在 API 19 及更低版本上不起作用的原因。在读取其 XML 的 target 节点时,Animator 会被 SDK 夸大:
Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);
AnimatorInflater 来自 SDK,因此膨胀的是 android.util.PathParser$PathDataNode 而不是 android.support.graphics.drawable.PathParser$PathDataNode。我想唯一可能的解决方法是让 Google 将 AnimatorInflater 集成到支持库中……
所以我们在这里处境艰难。 Google 承认 SDK 21-23 中的 VectorDrawable 实现包含错误(我注意到一些 SVG 上的 API 22 上存在一些绘图问题),但我们也不能使用支持库中的所有内容。所以,请记住,当涉及到VectorDrawables 时,必须在 19(或以下)、21、22、23 和 24(或以上)上进行测试……
编辑:截至今天(09/06/2017),Google 发布了支持库 25.4,它在 API 14+ 上向后移植路径变形。我想这个问题现在已经自动解决了(我还没有测试过)。
【问题讨论】:
标签: java android animation android-vectordrawable