即使在API 22设备上使用compat库动画矢量Drawable

时间:2017-04-27 09:57:54

标签: java android animation android-vectordrawable

我使用路径变形(仅在API 21及更高版本上可用)编写了一个可绘制的动画矢量。对于21以下的API,我使用简单旋转的后备动画。我使用动画矢量可绘制支持库(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?好吧,animationAnimatedVectorDrawable的一个实例。因此,路径变形工作正常。

所以我的问题是:为什么API 21-23设备没有选择内置实现,并且依赖于支持库,而API 24设备 拿起来?

  • 作为旁注,强制设备选择内置实现确实有效:

    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和#39中描述的ObjectAnimator相匹配; XML。其referent设置为VectorDrawableCompat,属性名称为pathData,值列表包含一个PropertyValuesHolder,其中包含两个关键帧,与我的开始和结束路径相匹配。结果:无法正常工作

使用内置版本(SDK 22),它不完全相同(但AnimatorSet并不完全在同一个地方,所以......):在{{ 1}},AnimatedVectorDrawableState列表有1个元素,直接是mAnimators(与支持版本的值相同)。结果:有效

我能看到的唯一相关区别是ObjectAnimator中的ValueAnimator。由于它有一些对drawable的引用,我猜它可能有一些类型检查忽略了PropertyValuesHolder类的支持库版本。但那时候那是纯粹的猜测。我一直在挖......

我终于明白了(并且接受了@ LewisMcGeary的回答,因为我在这个问题中没有提到我正在寻找问题背后的技术问题)。这里发生了什么。如上所述,在API 21-23上,支持库正在接管SDK的实现,以避免所述实现中的错误。因此,我们正在使用VectorDrawable和其他AnimatedVectorDrawableCompat类。一旦载体本身被加载,它就是动画的转向。

动画被委托给SDK [whatever]Compat,无论我们处理的API级别如何(至少在21岁以上,但我猜它在19岁时也是如此)以下)。为了设置原始类型的动画,ObjectAnimator有一个内部函数映射来调用以更改值。但是,在复杂类型上,它依赖于具有的特定方法签名存在于动画对象上。这是方法将值类型映射到相应的方法来调用ObjectAnimator(SDK,API 22):

PropertyValuesHolder

有趣的部分是 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)上调用方法的类是我们的Compat类:targetClass。我们正在寻找的方法(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

的路径数据

那么VectorDrawableCompat内容到底在哪里? typeVariants而不是支持者?这是因为动画的膨胀方式。正如我们所看到的,android.util.PathParser$PathDataNode正在将大部分工作委托给SDK,这就是为什么有些东西不适用于API 19及以下的原因。在阅读其XML的AnimatedVectorDrawableCompat节点时,SDK会对target进行夸大:

Animator

Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);来自SDK,因此会导致AnimatorInflater而不是android.util.PathParser$PathDataNode。我想唯一可能的解决办法是让Google将android.support.graphics.drawable.PathParser$PathDataNode集成到支持库中......

所以我们在这里处于困境。 Google承认SDK 21-23中的AnimatorInflater实现包含错误(我注意到某些SVG上的API 22上有一些绘图问题),但我们也无法使用支持库中的所有内容。所以,请记住,在VectorDrawable s ...

时,必须对19(或以下),21,22,23 24(或以上)进行测试。

编辑:截至今天(09/06/2017),Google发布了支持库25.4,它支持API 14+上的路径变形。我想这个问题现在已经自动解决了(我还没有测试过)。

1 个答案:

答案 0 :(得分:2)

AnimatedVectorDrawableCompat在内部进行版本检查,只有在版本为API 24或更高版本时(编写本文时)才委托给系统实现。

至于推理,似乎与您链接的问题一样,以避免早期API的内置实现出现问题。

最新的here's the git commit,提及this issue in the issue tracker有关渲染问题。

不幸的是,这确实意味着修复一些东西也会删除其他功能(例如路径变形)。我认为你在问题中使用的方法类型实际上是目前解决这个问题的唯一选择。