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

Mar*_*say 5 java animation android android-vectordrawable

我使用路径变形编写了一个可绘制的动画矢量(仅在API 21及更高版本上可用).我有一个回退动画使用简单的旋转API 21以下.我正在使用动画矢量drawable支持库(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();
}
Run Code Online (Sandbox Code Playgroud)

这适用于API 19和24,但不适用于API 22和23(我没有要测试的API 21设备).

API 19案例是合乎逻辑的:动画很简单,完全由支持库处理,它可以工作.大.

我希望任何API 21及更高版本的设备都能选择内置的矢量可绘制实现.但是,在调试时,我可以看到它animation实际上是一个实例AnimatedVectorDrawableCompat:因此,它不支持路径变形,并且动画不起作用.

那为什么它适用于API 24?嗯,animation是一个实例AnimatedVectorDrawable.因此,路径变形工作正常.

所以我的问题是:为什么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();
    }
    Run Code Online (Sandbox Code Playgroud)
  • 我还在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>
Run Code Online (Sandbox Code Playgroud)

并且两个路径资源:

<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>
Run Code Online (Sandbox Code Playgroud)

在API 22设备上,内置和支持版本(25.3.1)似乎Animator从我上面的AVD中膨胀相同,尽管具有不同的层次结构.

使用支持版本(25.3.1),AnimatorSet只有一个节点:一个AnimatorSet包含单个动画的节点,看起来与ObjectAnimatorAVD的XML中描述的匹配.它referent被设置为VectorDrawableCompat,属性名称是正确的pathData,值列表包含一个PropertyValuesHolder带有两个关键帧的单一,匹配我的开始和结束路径.结果:不起作用.

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

唯一的区别我可以看到的是ValueAnimatorPropertyValuesHolder.因为它有一些关于drawable的引用,我想它可能有一些类型检查忽略了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;
    }
Run Code Online (Sandbox Code Playgroud)

有趣的部分是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绝对没有办法知道如何更新我们的路径数据VectorDrawableCompat.

那么typeVariants内容来自哪里?而android.util.PathParser$PathDataNode不是支持一个?这是因为动画的膨胀方式.AnimatedVectorDrawableCompat正如我们所看到的,正在将大部分工作委托给SDK,这就是为什么有些东西不适用于API 19及以下版本.在读取targetXML 的节点时,AnimatorSDK会膨胀:

Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);
Run Code Online (Sandbox Code Playgroud)

AnimatorInflater来自SDK,并且因此膨胀一个android.util.PathParser$PathDataNode,而不是一个android.support.graphics.drawable.PathParser$PathDataNode.我想唯一可能解决的问题是Google将其集成AnimatorInflater到支持库中......


所以我们在这里处于困境.Google承认VectorDrawableSDK 21-23 的实现包含错误(我注意到某些SVG上的API 22上有一些绘图问题),但我们也无法使用支持库中的所有内容.所以,请记住,在19(或更低),21,22,23 24(或更高)的测试只是强制性VectorDrawable的...


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

Lew*_*ary 2

AnimatedVectorDrawableCompat仅当版本为 API 24 或更高版本(在撰写本文时)时,才会在内部进行版本检查并委托给系统实现。

至于推理,似乎正如您链接到的问题中提到的那样,以避免早期 API 的内置实现出现问题。

对于最近的一个,这是 git commit ,它在有关渲染问题的问题跟踪器中引用了此问题。

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