在Android中使用数据绑定将可绘制资源ID设置为ImageView的android:src。

117
我正在尝试使用数据绑定将可绘制资源ID设置为ImageView的android:src属性。
这是我的对象:
public class Recipe implements Parcelable {
    public final int imageResource; // Resource ID (e.g. R.drawable.some_image)
    public final String title;
    // ...

    public Recipe(int imageResource, String title /* ... */) {
        this.imageResource = imageResource;
        this.title = title;
    }

    // ...
}

这是我的布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="recipe"
            type="com.example.android.fivewaystocookeggs.Recipe" />
    </data>

    <!-- ... -->

    <ImageView
        android:id="@+id/recipe_image_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@{recipe.imageResource}" />

    <!-- ... -->

</layout>

最后,活动类:
// ...

public class RecipeActivity extends AppCompatActivity {

    public static final String RECIPE_PARCELABLE = "recipe_parcelable";
    private Recipe mRecipe;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mRecipe = getIntent().getParcelableExtra(RECIPE_PARCELABLE);
        ActivityRecipeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_recipe);
        binding.setRecipe(mRecipe);
    }

    // ...

}

它根本不显示图像。我做错了什么吗?
顺便说一下,它以标准方式完美运行:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_recipe);

    final ImageView recipeImageView = (ImageView) findViewById(R.id.recipe_image_view);
    recipeImageView.setImageResource(mRecipe.imageResource);

}
18个回答

129

截至2016年11月10日的答案

Splash在下面的评论中指出,不必使用自定义属性类型(如imageResource),我们可以创建多个android:src方法,如下所示:

public class DataBindingAdapters {

    @BindingAdapter("android:src")
    public static void setImageUri(ImageView view, String imageUri) {
        if (imageUri == null) {
            view.setImageURI(null);
        } else {
            view.setImageURI(Uri.parse(imageUri));
        }
    }

    @BindingAdapter("android:src")
    public static void setImageUri(ImageView view, Uri imageUri) {
        view.setImageURI(imageUri);
    }

    @BindingAdapter("android:src")
    public static void setImageDrawable(ImageView view, Drawable drawable) {
        view.setImageDrawable(drawable);
    }

    @BindingAdapter("android:src")
    public static void setImageResource(ImageView imageView, int resource){
        imageView.setImageResource(resource);
    }
}

旧回答

你总可以尝试使用适配器:

public class DataBindingAdapters {

    @BindingAdapter("imageResource")
    public static void setImageResource(ImageView imageView, int resource){
        imageView.setImageResource(resource);
    }
}

然后您可以像这样在xml中使用适配器

<ImageView
    android:id="@+id/recipe_image_view"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:scaleType="centerCrop"
    imageResource="@{recipe.imageResource}" />

请注意xml中的名称与BindingAdapter注释的名称(imageResource)匹配

DataBindingAdapters类不需要在特定位置声明,DataBinding机制将无论如何找到它 (我相信)


6
@YuriySeredyuk 不要这样做!哈哈。那样会覆盖你 整个应用程序 中 XML 中每一个 "android:src" 的使用,你绝对不想这样做。要让 imageResource 生效,你需要像我上面所做的那样更改 imageView 的 xml,数据绑定将确定在那里放置它。 - Joe Maher
1
好的,我明白了。现在使用<ImageView imageResource="@{recipe.imageResource}" />@BindingAdapter("imageResource")。我只是错过了你代码片段中的imageResource="@{recipe.imageResource}"部分 :) - Yuriy Seredyuk
1
这不需要是 app:imageResource 吗? - NameSpace
1
这样做将覆盖整个应用程序中xml中每个"android:src"的使用。数据绑定难道不够聪明,只将该属性应用于ImageView,因为在函数中定义了吗?我认为"android:src"更可取...请考虑Android本身对ImageView绑定适配器所做的事情:https://android.googlesource.com/platform/frameworks/data-binding/+/android-7.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/ImageViewBindingAdapter.java?autodive=0%2F - Splash
@hogar 我个人不会在适配器中编写此逻辑,我会单独处理1/100图像,然后将其传递。 - Joe Maher
显示剩余11条评论

101

根本不需要自定义BindingAdapter。只需使用:

app:imageResource="@{yourResId}"

它将正常工作。

查看这里以了解它的工作原理。


7
这个答案很棒,在2020年左右应该会有更多的赞。 - JohnnyLambada
2
绝对是最好且最简单的答案。 - luckyhandler
2
这似乎是截至2020年末最好、最合适的答案。 - mcy
我正在查看ImageView类和setImageResource方法,似乎最终是在resolveUri上解决的,并且没有进行任何验证。所以这对于Int可以工作,但我想知道如果是Int?会发生什么。当绑定被执行时,例如如果其他东西调用了executePendingBindings,那么非空值将默认为零,可空值将默认为null。 - cutiko
1
绝对有效 ;) 非常感谢 - Nabil Ait Brahim
请记得在xml中添加<data><import type="your.package.R"/></data> - Zhou Hongbo

25

定义:

@BindingAdapter({"android:src"})
public static void setImageViewResource(ImageView imageView, int resource) {
    imageView.setImageResource(resource);
}

使用:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:scaleType="center"
    android:src="@{viewModel.imageRes, default=@drawable/guide_1}"/>

我在哪里添加这个方法? - Myat Min Soe
支持在任何类中添加它,也许您可以创建一个ImageDataBindingAdapter.class。 - qinmiao

18
Maher Abuthraa的答案的基础上,这是我在XML中最终使用的内容。
android:src="@{context.getDrawable(recipe.imageResource)}"

在绑定表达式中,context变量可用,无需任何导入。此外,也不需要自定义BindingAdapter。唯一的注意事项是,getDrawable方法仅在API 21及以上版本可用。

1
通过使用 <import type="androidx.core.content.ContextCompat"/>android:src="@{ContextCompat.getDrawable(context, recipe.imageResource)}",您甚至可以在更低的API级别上实现。 - Bruno Bieri

13

你可以通过DataBindingAdapter做更多的事情

  • 你可以通过数据绑定设置图片URL文件位图字节数组可绘制对象可绘制对象ID,几乎可以设置任何类型的数据。
  • 你还可以通过将多个参数传递给绑定适配器来设置错误图片和占位图片。

设置任何以下类型:

android:src="@{model.profileImage}"

android:src="@{roundIcon ? @drawable/ic_launcher_round : @drawable/ic_launcher_round}"

android:src="@{bitmap}"

android:src="@{model.drawableId}"

android:src="@{@drawable/ic_launcher}"

android:src="@{file}"

android:src="@{`https://placekitten.com/200/200`}"

对于mipmap资源
android:src="@{@mipmap/ic_launcher}" <!--This will show Token recognition error at '@mipmap -->


android:src="@{R.mipmap.ic_launcher}" <!-- correct with improt R class -->

设置错误图片或占位图片
placeholderImage="@{@drawable/img_placeholder}"
errorImage="@{@drawable/img_error}"


<ImageView
    placeholderImage="@{@drawable/ic_launcher}"
    errorImage="@{@drawable/ic_launcher}"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:src="@{`https://placekitten.com/2000/2000`}"
    />

测试了所有类型

SC

这就可以通过一个单一的绑定适配器实现。只需复制这个方法项目即可。
public class BindingAdapters {
    @BindingAdapter(value = {"android:src", "placeholderImage", "errorImage"}, requireAll = false)
    public static void loadImageWithGlide(ImageView imageView, Object obj, Object placeholder, Object errorImage) {
        RequestOptions options = new RequestOptions();
        if (placeholder instanceof Drawable)
            options.placeholder((Drawable) placeholder);
        if (placeholder instanceof Integer)
            options.placeholder((Integer) placeholder);

        if (errorImage instanceof Drawable)
            options.error((Drawable) errorImage);
        if (errorImage instanceof Integer)
            options.error((Integer) errorImage);

        RequestManager manager = Glide.with(App.getInstance()).
                applyDefaultRequestOptions(options);
        RequestBuilder<Drawable> builder;

        if (obj instanceof String) {
            builder = manager.load((String) obj);
        } else if (obj instanceof Uri)
            builder = manager.load((Uri) obj);
        else if (obj instanceof Drawable)
            builder = manager.load((Drawable) obj);
        else if (obj instanceof Bitmap)
            builder = manager.load((Bitmap) obj);
        else if (obj instanceof Integer)
            builder = manager.load((Integer) obj);
        else if (obj instanceof File)
            builder = manager.load((File) obj);
        else if (obj instanceof Byte[])
            builder = manager.load((Byte[]) obj);
        else builder = manager.load(obj);
        builder.into(imageView);
    }
}

我使用Glide加载所有对象的原因

如果你问我为什么我使用Glide来加载drawable/资源id,而不是使用imageView.setImageBitmap();imageView.setImageResource();?原因是

  • Glide是一个高效的图像加载框架,它包装了媒体解码、内存和磁盘缓存。所以你不需要担心大尺寸的图像和缓存问题。
  • 为了保持加载图像的一致性。现在所有类型的图像资源都通过Glide加载。

如果你使用PicassoFresco或其他图像加载库,你可以在loadImageWithGlide方法中进行更改。


errorImage="@{@drawable/ic_launcher}"。这个东西对我来说甚至无法编译。 - Vivek Mishra
@VivekMishra 或许你的 ic_launcher 在 mipmap 文件夹里?尝试使用 @mipmap/ic_launcher。 - Khemraj Sharma
@VivekMishra 你能贴出你相关的错误日志吗?你是否将这个方法添加到了你的绑定工具类中? - Khemraj Sharma
****/数据绑定错误 **** 消息:在com.zuowei.circleimageview.CircleImageView上找不到值类型为java.lang.String的属性'app:src'的getter。我尝试过使用android和app命名空间,但都没有起作用。我还将参数中的默认ImageView替换为CircleImageView。 - Vivek Mishra
另外,我已经在一个单独的类中创建了绑定适配器。 - Vivek Mishra
显示剩余4条评论

13

在创建自己的@BindingAdapter时,永远不要覆盖标准SDK属性!

这种做法有很多不好的原因,比如: 它会阻止在Android SDK更新中获得新修复的好处。而且它可能会让开发人员感到困惑,并且对于可重用性来说肯定是棘手的(因为覆盖是意外的)。

你可以使用不同的命名空间,比如:

custom:src="@{recipe.imageResource}"

或者

mybind:src="@{recipe.imageResource}"

------ 开始 更新 2018-07-02

不建议使用命名空间,最好依赖于前缀或不同的名称,例如:

app:custom_src="@{recipe.imageResource}"

或者

app:customSrc="@{recipe.imageResource}"

------ 2018-07-02 更新结束

然而,我会推荐一个不同的解决方案:

android:src="@{ContextCompat.getDrawable(context, recipe.imageResource)}"

上下文视图在绑定表达式 @{ ... } 中始终可用。

1
我认为应该尽可能避免在xml中编写代码-它不可测试,可能会堆积并且不明显。但我同意重载标准属性可能会令人困惑。我认为最好的方法是用不同的名称命名新的属性,在这种情况下是“srcResId”,但仍然使用BindingAdapter。 - Kirill Starostin
“编辑”或“更新”[不应出现在答案中](https://meta.stackexchange.com/questions/131009/what-should-i-keep-out-of-my-posts-and-titles/131011#131011)。答案应该像是今天写的一样(有完整的修订历史记录;从当前版本中删除内容不会丢失任何信息)。 - undefined

7

对于 Kotlin,将以下内容放入顶层的 utils 文件中,无需静态/伴生对象:

@BindingAdapter("android:src")
fun setImageViewResource(view: ImageView, resId : Int) {
    view.setImageResource(resId)
}

7

完全不需要自定义BindingAdapter。只需使用这个

data:

<data>
    <import type="com.example.R"/>
      :
</data>

ImageView:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:imageResource="@{gender == 0 ? R.drawable.male : R.drawable.female}" />

你帮了我这个忙,通常情况下我会避免传递资源ID,但由于第三方提供的自定义UI库,我需要这样做。谢谢! - stetro

3
这适用于我。我本来想将其作为评论添加到@hqzxzwb的答案中,但由于声望限制,无法实现。
我在我的视图模型中有这个。
var passport = R.drawable.passport

然后在我的xml中,我有

android:src="@{context.getDrawable(model.passort)}"

And thats it


1
好的,但是你忘了提到你必须导入上下文。你能更新一下你的答案吗? - deadfish

2

您可以执行以下操作:

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"

我们可以,但这样做应该达到什么目的? - undefined

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