ResourcesCompat.getDrawable()与AppCompatResources.getDrawable()的区别

75

我对这两个API有些困惑。

ResourcesCompat.getDrawable(Resources res, int id, Resources.Theme theme)

返回与特定资源ID相关联并为指定主题进行样式设置的可绘制对象。根据底层资源,将返回各种类型的对象 - 例如,纯色、PNG图像、可缩放图像等。

在 API 级别 21 之前,不会应用主题,此方法只是通过调用 getDrawable(int) 方法来调用。

AppCompatResources.getDrawable(Context context, int resId)

返回与特定资源 ID 相关联的可绘制对象。

此方法支持在平台不支持向量和动画矢量资源的设备上膨胀。

问题

  1. 除了 vector 膨胀外,这两个类之间有什么显着差异吗?
  2. 我应该偏向哪一个并为什么?
1. 这两个类之间最显着的区别是,ResourcesCompat.getDrawable() 方法支持主题样式,并且可以返回各种类型的可绘制对象,而 AppCompatResources.getDrawable() 方法仅支持向量和动画矢量资源的膨胀。 2. 如果您需要为特定主题获取可绘制对象或需要能够获得各种类型的可绘制对象,则应该使用 ResourcesCompat.getDrawable()。否则,如果您需要支持在平台不支持向量和动画矢量资源的设备上膨胀,请使用 AppCompatResources.getDrawable()。
4个回答

110

在一些测试后,我得出了以下的理解:

ContextCompat.getDrawable(@NonNull Context context, @DrawableRes int resId)

ResourcesCompat.getDrawable(@NonNull Resources res, @DrawableRes int id, @Nullable Theme theme)

AppCompatResources.getDrawable(@NonNull Context context, @DrawableRes int resId)

VectorDrawableCompat.create(@NonNull Resources res, @DrawableRes int resId, @Nullable Theme theme

首先我看到的是可以使用VectorDrawableCompatResourcesCompat指定一个主题。

I) 在不使用

AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);在Application类的onCreated方法中

1) 对于矢量图像

  • API >= 21

  • ContextCompat表现良好

  • ResourcesCompat表现良好

  • AppCompatResources表现良好

  • VectorDrawableCompat表现良好

  • API < 21

  • ContextCompat 崩溃

  • ResourcesCompat 崩溃

  • AppCompatResources表现良好

  • VectorDrawableCompat表现良好

2) 对于普通图像

  • 在所有API级别上
  • ContextCompat表现良好
  • ResourcesCompat表现良好
  • AppCompatResources表现良好
  • VectorDrawableCompat 崩溃

II) 使用

AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);在Application类的onCreated方法中

1) 对于矢量图像

  • 在所有API级别上
  • ContextCompat表现良好
  • ResourcesCompat表现良好
  • AppCompatResources表现良好
  • VectorDrawableCompat表现良好

2) 对于普通图像

  • 在所有API级别上
  • ContextCompat表现良好
  • ResourcesCompat表现良好
  • AppCompatResources表现良好
  • VectorDrawableCompat 崩溃

21
很好的总结,但不幸的是它只是展示了支持库有多么混乱 :). 你有1002345种方法来创建可绘制对象(动画或非动画)。我们应该只有一种独特的方式来实现它,如果动画不被支持,他们应该尝试将其转换为简单的可绘制对象图片,而不是用ResourceNotFound导致应用程序崩溃。 - Stoycho Andreev
2
那么 AppCompatResources 是唯一一个没有崩溃的吗? - cmak
非常有信息量 - undefined

45

看两种方法的源代码,它们似乎非常相似。如果你没有向量,你可能可以随便使用其中的任何一个。

ResourcesCompat.getDrawable()会在API 21或更高版本上调用Resources#getDrawable(int, theme)。它还支持Android API 4+。它不过是这样:

public Drawable getDrawable(Resources res, int id, Theme theme)
        throws NotFoundException {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 21) {
        return ResourcesCompatApi21.getDrawable(res, id, theme);
    } else {
        return res.getDrawable(id);
    }
}

ResourcesCompatApi21 仅仅调用了 res.getDrawable(id, theme)。这意味着如果设备不支持矢量图,它将不会允许绘制矢量图形。但是,它仍然允许您传递一个主题。

与此同时,AppCompatResources.getDrawable(Context context, int resId) 的代码更改最终变成了这样:

Drawable getDrawable(@NonNull Context context, @DrawableRes int resId, boolean failIfNotKnown) {
    checkVectorDrawableSetup(context);

    Drawable drawable = loadDrawableFromDelegates(context, resId);
    if (drawable == null) {
        drawable = createDrawableIfNeeded(context, resId);
    }
    if (drawable == null) {
        drawable = ContextCompat.getDrawable(context, resId);
    }

    if (drawable != null) {
        // Tint it if needed
        drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
    }
    if (drawable != null) {
        // See if we need to 'fix' the drawable
        DrawableUtils.fixDrawable(drawable);
    }

    return drawable;
}

因此,如果可能,它将尝试绘制资源,否则它会查找 ContextCompat 版本以获取资源。然后,如果必要,甚至会对其进行着色。但是,此方法仅支持API 7+。

因此,我猜想你需要决定是否应该使用以下两种方式之一:

  1. 您是否需要支持 API 4、5 或 6?

    • 是:只能使用 ResourcesCompatContextCompat
    • 否:继续到步骤#2。
  2. 您是否绝对需要提供自定义主题?

    • 是:只能使用 ResourcesCompat
    • 否:使用 AppCompatResources

你是否绝对需要提供自定义主题?是的:别无选择,只能使用ResourcesCompat。谢谢你澄清了这一点。 - Sebastian Roth
使用AppCompatResources加载矢量图形资源可以获得更好的图片质量(分辨率)。 - ultraon

28

ContextCompat

ResourcesCompatContextCompat以及support-v4中任何以Compat结尾的类都可以帮助你避免在代码中到处写if (Build.VERSION.SDK_INT >= X)这样的检查。就是这样。例如,你可以使用ContextCompat代替:

final Drawable d;
if (Build.VERSION.SDK_INT < 21) {
    // Old method, drawables cannot contain theme references.
    d = context.getResources().getDrawable(R.drawable.some_image);
} else {
    // Drawables on API 21 can contain theme attribute references.
    // Context#getDrawable only exists since API 21.
    d = context.getDrawable(R.drawable.some_image);
}

你可以写

final Drawable d = ContextCompat.getDrawable(context, R.drawable.some_image);

例如,评论中描述的限制适用。

// This line is effectively equivalent to the above.
ResourcesCompat.getDrawable(context.getResources(), R.drawable.some_image, context.getTheme());

据文档所说,在Lollipop之前,实际上不会应用主题属性。但是您无需编写if检查,因为您并未在旧设备上使用新的API,因此您的代码不会崩溃。

AppCompatResources

AppCompatResources则会帮助您将新功能带到旧平台(支持向量、颜色状态列表中的主题引用)。

我应该优先选择哪一个,为什么?

使用AppCompatResources以便与appcompat-v7库的其余部分获得一致的结果。您将获得:

  • getColorStateList可以解析颜色及主题属性引用(例如,android:alpha="?android:disabledAlpha"),
  • getDrawable支持在所有平台上充气向量图形,并且这些向量可绘制图形也理解主题属性引用(例如,android:tint="?colorControlNormal"),
  • appcompat-v7 drawables和颜色,如勾选标记或单选按钮,将具有由提供的上下文主题定义的适当颜色,
  • 如果上述不适用,则仍会回退到ContextCompat

1

API 19+的解决方法

package com.example;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;

/**
 * https://dev59.com/61gQ5IYBdhLWcg3wSh91#48237058
 */
public class AppDrawableCompat {

    public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
        try {
            return AppCompatResources.getDrawable(context, resId);
        } catch (Resources.NotFoundException e1) {
            try {
                return ContextCompat.getDrawable(context, resId);
            } catch (Resources.NotFoundException e2) {
                return VectorDrawableCompat.create(context.getResources(), resId, context.getTheme());
            }
        }
    }

    @Nullable
    public static Drawable findDrawable (@NonNull Context context, @DrawableRes int resId) {
        try {
            return getDrawable(context, resId);
        } catch (Resources.NotFoundException e) {
            return null;
        }
    }

    public static void setViewBackgroundDrawable(@NonNull View view, @NonNull Context context, @DrawableRes int resId) {
        Drawable drawable = findDrawable(context, resId);
        if (drawable != null) {
            view.setBackground(drawable);
        }
    }
}

使用示例(MainActivity#onCreate)
ImageView icon = findViewById(R.id.icon);
AppDrawableCompat.setViewBackgroundDrawable(icon, this, R.drawable.bg_icon);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接