【问题标题】:Animated Vector Drawable using compat library even on API 22 device即使在 API 22 设备上也可以使用兼容库的动画矢量绘图
【发布时间】: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?好吧,animationAnimatedVectorDrawable 的一个实例。因此,路径变形可以正常工作。

所以我的问题是:为什么 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数组中只有一项,所以returnValnull结尾,最后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


    【解决方案1】:

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

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

    对于最近的一个here's the git commit,它指的是this issue in the issue tracker关于渲染问题。

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

    【讨论】:

    • 嗯,这就是最初的想法,但即使在 API 22 上,也使用了平台的 AnimatedVectorDrawable 委托。我注意到在 22 和 24 思想之间转移到 AnimatorSet 的动画有所不同。今晚我会更深入地研究它。
    • 我做了更多的研究,并用更多的细节更新了这个问题。据我所见,在 API 22 设备上,compat 25 和 SDK 实现最终都读取相同的ObjectAnimator - 一个针对VectorDrawable,另一个针对VectorDrawableCompat。我猜平台的ObjectAnimator 进行了一些类型检查并且无法识别兼容版本(我还没有证据)。
    • 所以,我接受了您的回答,因为您实际上回答了我提出的问题(即使这不是我真正想要的,这是技术解释,但我猜你无法读懂我的想法:-))。我找到了问题的技术原因,我将其添加到问题中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-30
    • 2018-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-11
    相关资源
    最近更新 更多