117

I'm trying to set drawable resource ID to android:src of ImageView using data binding

Here is my object:

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;
    }

    // ...
}

Here is my layout:

<?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>

And finally, activity class:

// ...

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);
    }

    // ...

}

It doesn't display image at all. What am I doing wrong?

BTW, it was perfectly working with standard way:

@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);

}
Yuriy Seredyuk
  • 1,643
  • 2
  • 15
  • 18

18 Answers18

130

Answer as of Nov 10 2016

Splash's comment below has highlighted that it is not necessary to use a custom property type (like imageResource), we can instead create multiple methods for android:src like so:

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);
    }
}

Old Answer

You could always try to use an adapter:

public class DataBindingAdapters {

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

You can then use the adapter in your xml like so

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

Be sure to notice that the name within the xml matches the BindingAdapter annotation (imageResource)

The DataBindingAdapters class doesn't need to be declared anywhere in particular, the DataBinding mechanics will find it no matter (i believe)

Marvin Effing
  • 2,693
  • 3
  • 20
  • 35
Joe Maher
  • 5,354
  • 5
  • 28
  • 44
  • It works by using `@BindingAdapter`. But, value should be `android:src`, not `imageResource`: `@BindingAdapter("android:src")`. Thank you! – Yuriy Seredyuk Mar 05 '16 at 03:14
  • 6
    @YuriySeredyuk Nooooo! haha please. Doing that will override every single use of "android:src" within the xml across your **entire application** which you definitely **DON'T** want to be doing. To get imageResource to work you have to change the xml for the imageView like i have done above, the databinding will work out what to put there – Joe Maher Mar 05 '16 at 03:17
  • 1
    OK, I understood idea. Now using `` and `@BindingAdapter("imageResource")`. I just missed `imageResource="@{recipe.imageResource}"` part from your code snipped :) – Yuriy Seredyuk Mar 05 '16 at 04:05
  • So then this line here `@BindingAdapter("imageResource")` defines a new key **imageResource** you can use in the xml to reference the resource ID. It took me a bit to make that connection.. I think I got it though, Thanks! – Gene Bo May 06 '16 at 00:28
  • 1
    Doesn't this need to be `app:imageResource`? – NameSpace Aug 17 '16 at 06:55
  • @NameSpace including app will make no difference, the only difference i have seen my self is that the ide's will no longer flag it as invalid – Joe Maher Aug 17 '16 at 22:26
  • 1
    "Doing that will override every single use of "android:src" within the xml across your entire application" Isn't databinding smart enough to only apply that attribute to ImageView, because that's whats defined in the function? I think "android:src" would be preferable....consider what Android itself doe for ImageView binding adapters: 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 Oct 31 '16 at 18:11
  • Oh yes sorry i did intend that to mean each use of "android:src" within imageview's across the application, but you do make a good poiint, i didn't realise the databinding was smart enough to match up the type given like in that example you have reference. Will modfiy my answer – Joe Maher Oct 31 '16 at 22:49
  • I have similar problem. Depending on some cases I need to show one of 100 pngs, how to do that with data binding? – svarog Jun 15 '17 at 15:25
  • @hogar i personally wouldn't have my logic for doing that within the adapter, id work out the 1/100 image separately and then pass it on – Joe Maher Jun 15 '17 at 23:12
  • @Joe Maher I do not get your point. Can you please clarify this a little bit? Thanks – svarog Jun 16 '17 at 08:41
  • what is imageView passed as a parameter in setImageDrawable method – Manish Butola Feb 27 '18 at 06:22
  • Using a `@BindingAdapter("android:src")` that takes an `int` as the parameter clashes with `android:src="@{aBoolean ? @color/red : @color/green}"`. The color gets also passed to this adapter as an int and therfore the resource for `imageView.setImageResource(...)` cannot be resolved. – Jakob Ulbrich Apr 05 '18 at 14:33
  • No need for a BindingAdapter. The method `ImageView.setImageResource` has a setter style name. Databinding binds setters automatically. – hqzxzwb Nov 07 '19 at 10:46
  • can be converted to Kotlin as extensions (a bit of code) – user924 Jan 27 '21 at 07:46
100

There is no need for a custom BindingAdapter at all. Just use

app:imageResource="@{yourResId}"

and it will work fine.

Check this for how it works.

hqzxzwb
  • 4,661
  • 2
  • 14
  • 15
  • I'm taking a look at the `ImageView` class and following the `setImageResource` method, seems that eventually is solved on `resolveUri` and there is if not zero validation. So that would work for `Int` I wonder what could happen if `Int?`. When bindings are executed, by example if something else call `executePendingBindings` then not nullable are defaulted to zero, nullables to null. – cutiko Oct 20 '20 at 14:59
  • Remeber to add`` in xml – Zhou Hongbo Mar 28 '22 at 08:28
25

define:

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

use:

<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}"/>
qinmiao
  • 5,559
  • 5
  • 36
  • 39
18

Building upon the answer from Maher Abuthraa, this is what I ended up using in the XML:

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

The context variable is available in binding expression without any imports. Also, no custom BindingAdapter necessary. Only caveat: the method getDrawable is only available since API 21.

Tom Ladek
  • 688
  • 11
  • 19
  • 1
    By using `` and the `android:src="@{ContextCompat.getDrawable(context, recipe.imageResource)}"` you can reach even lower API level. – Bruno Bieri Apr 14 '21 at 12:00
13

Never override standard SDK attributes when you create your own @BindingAdapter!

This is not a good approach for many reasons like: it's gonna prevent obtaining benefits of new fixes on Android SDK update on that attribute. Also it might confuse developers and surely tricky for reusability (because it's un-exptected to be overrided )

you may use different namespace like:

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

or

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

------ start Update 2.Jul.2018

Namespace is not recommended to be used, so better to rely on prefix or different name as:

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

or

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

------ end Update 2.Jul.2018

However, I would recommend different solution as:

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

context view is always available inside binding expression @{ ... }

Maher Abuthraa
  • 17,493
  • 11
  • 81
  • 103
  • 1
    I think that code inside xml should be avoided as much as possible - it is not testable, it can pile up and it is not obvious. But i agree that overloading standard attributes can be confusing. I think the best way is to name new attribute differently, in this case "srcResId", but still use a BindingAdapter – Kirill Starostin Jun 14 '19 at 06:33
13

###The more you can do with DataBindingAdapter

###Set any of these types:

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`}"

And for the mipmap resources

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


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

Set Error image/ Placeholder image

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`}"
    />

###Tested all the types

SC

So that becomes possible with single binding adapter. Just copy this method project.

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);
    }
}

###Reason I used Glide to load all objects

If you ask me why I used Glide to load drawable/ resource id, instead I could use imageView.setImageBitmap(); or imageView.setImageResource();. So the reason is that

  • Glide is an efficient image loading framework that wraps media decoding, memory and disk caching. So you need not to worry about large size images and cache.
  • To make consistency while loading image. Now all types of image resources are loaded by Glide.

If you use Piccaso, Fresso or any other image loading library, you can make changes in loadImageWithGlide method.

Rahul
  • 579
  • 1
  • 8
  • 18
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
8

For Kotlin put this to a top level utils file, no static / companion context needed:

@BindingAdapter("android:src")
fun setImageViewResource(view: ImageView, resId : Int) {
    view.setImageResource(resId)
}
joecks
  • 4,539
  • 37
  • 48
8

There is no need for a custom BindingAdapter at all. Just use.

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}" />
luibel
  • 81
  • 1
  • 2
  • You helped me with this normally I would avoid passing resource ids, but in my case it was needed because of a custom UI-lib provided by third party. Thanks! – stetro Sep 09 '21 at 08:37
4

This work for me. i would have add it to @hqzxzwb answer as comment but due to reputation limitations.

I have this in my view Model

var passport = R.drawable.passport

Then in my xml, I have

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

And thats it

Raines
  • 93
  • 2
  • 11
3
public Drawable getImageRes() {
        return mContext.getResources().getDrawable(R.drawable.icon);
    }

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="center"
    android:src="@{viewModel.imageRes}"/>
msevgi
  • 4,828
  • 2
  • 24
  • 30
3

you can do the following

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"
Mr. Alien
  • 153,751
  • 34
  • 298
  • 278
Fahad Alotaibi
  • 416
  • 3
  • 9
2

I am not an expert in Android but I spent hours trying to decipher the existing solutions. The good thing is that I grasped the whole idea of data binding using BindingAdapter a bit better. For that, I am at least thankful for the existing answers (although heavily incomplete). Here a complete breakdown of the approach:

I will also use the BindingAdapter in this example. Preparing the xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="model"
            type="blahblah.SomeViewModel"/>
    </data>

    <!-- blah blah -->

    <ImageView
        android:id="@+id/ImageView"
        app:appIconDrawable="@{model.packageName}"/>

    <!-- blah blah -->
</layout>

So here I am keeping only the important stuff:

  • SomeViewModel is my ViewModel I use for data binding. You can also use a class that extends BaseObservable and use @Bindable. However, the BindingAdapter in this example, doesn't have to be in a ViewModel or BaseObservable class! A plain class will do! This will be illustrated later.
  • app:appIconDrawable="@{model.packageName}". Yes... this was really causing me headaches! Let's break it down:
    • app:appIconDrawable: This can be anything: app:iCanBeAnything! Really. You can also keep "android:src"! However, take a note on your choice, we will use it later!
    • "@{model.packageName}": If you worked with data binding, this is familiar. I'll show how this is used later.

Let's assume we use this simple Observable class:

public class SomeViewModel extends BaseObservable {
   private String packageName; // this is what @{model.packageName}
                               // access via the getPackageName() !!!
                               // Of course this needs to be set at some
                               // point in your program, before it makes
                               // sense to use it in the BindingAdapter.

   @Bindable
   public String getPackageName() {
       return packageName;
   }

   public void setPackageName(String packageName) {
       this.packageName = packageName;
       notifyPropertyChanged(BR.packageName);
   }

   // The "appIconDrawable" is what we defined above! 
   // Remember, they have to align!! As we said, we can choose whatever "app:WHATEVER".
   // The BindingAdapter and the xml need to be aligned, that's it! :)
   //
   // The name of the function, i.e. setImageViewDrawable, can also be 
   // whatever we want! Doesn't matter.
   @BindingAdapter({"appIconDrawable"})
   public static void setImageViewDrawable(ImageView imageView, String packageName) {
       imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
   }
}

As promised, you can also move the public static void setImageViewDrawable(), to some other class, e.g. maybe you can have a class that has a collection of BindingAdapters:

public class BindingAdapterCollection {
   @BindingAdapter({"appIconDrawable"})
   public static void setImageViewDrawable(ImageView imageView, String packageName) {
       imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
   }
}

Another important remark is that in my Observable class I used String packageName to pass extra info to the setImageViewDrawable. You can also choose for example int resourceId, with the corresponding getters/setters, for which the adapter becomes:

public class SomeViewModel extends BaseObservable {
   private String packageName; // this is what @{model.packageName}
                               // access via the getPackageName() !!!
   private int resourceId;     // if you use this, don't forget to update
                               // your xml with: @{model.resourceId}

   @Bindable
   public String getPackageName() {
       return packageName;
   }

   public void setPackageName(String packageName) {
       this.packageName = packageName;
       notifyPropertyChanged(BR.packageName);
   }

   @Bindable
   public int getResourceId() {
       return packageName;
   }

   public void setResourceId(int resourceId) {
       this.resourceId = resourceId;
       notifyPropertyChanged(BR.resourceId);
   }

   // For this you use: app:appIconDrawable="@{model.packageName}" (passes String)
   @BindingAdapter({"appIconDrawable"})
   public static void setImageViewDrawable(ImageView imageView, String packageName) {
       imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
   }

   // for this you use: app:appIconResourceId="@{model.resourceId}" (passes int)
   @BindingAdapter({"appIconResourceId"})
   public static void setImageViewResourceId(ImageView imageView, int resource) {
       imageView.setImageResource(resource);
   }
}
Tanasis
  • 795
  • 1
  • 9
  • 21
1

Using Fresco(facebook image library)

 public class YourCustomBindingAdapters {

    //app:imageUrl="@{data.imgUri}"
    @BindingAdapter("bind:imageUrl")
    public static void loadImage(SimpleDraweeView imageView, String url) {
        if (url == null) {
            imageView.setImageURI(Uri.EMPTY);
        } else {
            if (url.length() == 0)
                imageView.setImageURI(Uri.EMPTY);
            else
                imageView.setImageURI(Uri.parse(url));
        }
    }
}
최봉재
  • 4,039
  • 3
  • 16
  • 21
1

In your view state or view model class;

 fun getSource(context: Context): Drawable? {
        return ContextCompat.getDrawable(context, R.drawable.your_source)
    }

In your XML;

<androidx.appcompat.widget.AppCompatImageButton
   .
   .
   .
   android:src="@{viewState.getSource(context)}"
Cafer Mert Ceyhan
  • 1,640
  • 1
  • 13
  • 17
1

Key point of solution is we need to give the type as type="android.graphics.drawable.Drawable"

Explanation is in the Below

Let's say we have 2 layout first_layout.xml and second_layout.xml and We will send drawable from first to second.

In first_layout.xml

<include
    android:id="@+id/home_last_trip"
    layout="@layout/second_layout.xml"
    app:myCustomImage="@{someCondition == 1 ? @drawable/your_image_1 :@drawable/your_image_1 }"/>

In second_layout.xml

<data>
    <variable
        name="myCustomImage"
        type="android.graphics.drawable.Drawable" />
</data>

You can use this data like this:

    <ImageView
        android:id="@+id/left_icon"
        android:layout_width="@dimen/_25"
        android:layout_height="@dimen/_25"
        android:src="@{myCustomImage}"/>
Tarık
  • 99
  • 6
0
<?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"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
           name="model"
           type="YourViewModel"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="?android:attr/selectableItemBackground"
          android:paddingStart="@dimen/dp16"
          android:paddingTop="@dimen/dp8"
          android:paddingEnd="@dimen/dp8"
          android:paddingBottom="@dimen/dp8">

          <ImageView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" 
              android:src="@{model.selected ? @drawable/check_fill : @drawable/check_empty}"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Fakhriddin Abdullaev
  • 4,169
  • 2
  • 35
  • 37
0

set image like this,

  <ImageView
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:src="@{model.isActive ? @drawable/white_activated_icon :@drawable/activated_icon}"
        tools:src="@mipmap/white_activated_icon" />
luttu android
  • 1,443
  • 16
  • 22
0

One more example using @IdRes and @BindingAdapter

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="hello.R" />
    </data>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        app:show_image="@{R.drawable.image}" />
</layout>
@BindingAdapter("show_image")
public static void loadImage(ImageView view, @IdRes int imageId) {
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205