Establecer ID de recurso dibujable en Android: src para ImageView usando enlace de datos en Android

91

Estoy tratando de establecer el ID de recurso dibujable en android: src de ImageView usando el enlace de datos

Aquí está mi objeto:

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

    // ...
}

Aquí está mi diseño:

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

Y finalmente, clase de actividad:

// ...

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

    // ...

}

No muestra ninguna imagen. ¿Qué estoy haciendo mal?

Por cierto, estaba funcionando perfectamente con la forma estándar:

@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
fuente

Respuestas:

112

Respuesta al 10 de noviembre de 2016

El comentario de Splash a continuación ha resaltado que no es necesario usar un tipo de propiedad personalizado (como imageResource), en su lugar podemos crear múltiples métodos para android:srcasí:

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

Respuesta antigua

Siempre puedes intentar usar un adaptador:

public class DataBindingAdapters {

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

Luego puede usar el adaptador en su xml así

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

Asegúrese de notar que el nombre dentro del xml coincide con la anotación BindingAdapter (imageResource)

La clase DataBindingAdapters no necesita declararse en ningún lugar en particular, la mecánica de DataBinding la encontrará sin importar (creo)

Joe Maher
fuente
Funciona usando @BindingAdapter. Sin embargo, el valor debe ser android:src, no imageResource: @BindingAdapter("android:src"). ¡Gracias!
Yuriy Seredyuk
5
@YuriySeredyuk ¡Nooooo! jaja por favor. Hacer eso anulará cada uso de "android: src" dentro del xml en toda su aplicación, lo que definitivamente NO quiere hacer. Para que imageResource funcione, debe cambiar el xml de imageView como lo hice anteriormente, el enlace de datos resolverá qué poner allí
Joe Maher
1
OK, entendí la idea. Ahora usando <ImageView imageResource="@{recipe.imageResource}" />y @BindingAdapter("imageResource"). Me perdí imageResource="@{recipe.imageResource}"parte de tu código recortado :)
Yuriy Seredyuk
1
¿No tiene que ser así app:imageResource?
NameSpace
1
"Hacer eso anulará cada uso de" android: src "dentro del xml en toda su aplicación" ¿No es el enlace de datos lo suficientemente inteligente como para aplicar ese atributo solo a ImageView, porque eso es lo que está definido en la función? Creo que "android: src" sería preferible ... considere lo que hace Android para los adaptadores de enlace de ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash
41

No hay necesidad de una costumbre BindingAdapteren absoluto. Solo usa

app:imageResource="@{yourResId}"

y funcionará bien.

Compruebe esto para saber cómo funciona.

hqzxzwb
fuente
2
esto debería tener más votos positivos, ya que es una gran respuesta alrededor de 2020
JohnnyLambada
definitivamente, la mejor y más simple respuesta
luckyhandler
Esta parece la mejor y más apropiada respuesta a fines de 2020
mcy
Estoy echando un vistazo a la ImageViewclase y siguiendo el setImageResourcemétodo, parece que finalmente se resuelve resolveUriy hay, si no cero, validación. Así que eso funcionaría porque Intme pregunto qué podría pasar si Int?. Cuando se ejecutan los enlaces, por ejemplo, si otra llamada, executePendingBindingsentonces los que no aceptan valores nulos se establecen por defecto en cero, los valores nulos en nulos.
cutiko
25

definir:

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

utilizar:

<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
fuente
¿Dónde agrego ese método
myatmins
admite agregarlo en cualquier clase, tal vez puedas crear un ImageDataBindingAdapter.class.
qinmiao
12

¡Nunca anule los atributos estándar del SDK cuando cree los suyos propios @BindingAdapter!

Este no es un buen enfoque por muchas razones como: evitará obtener beneficios de nuevas correcciones en la actualización del SDK de Android en ese atributo. También puede confundir a los desarrolladores y seguramente es complicado para la reutilización (porque no se espera que se anule)

puede usar diferentes espacios de nombres como:

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

o

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

------ iniciar actualización 2.Jul.2018

No se recomienda el uso del espacio de nombres, por lo que es mejor confiar en el prefijo o en un nombre diferente como:

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

o

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

------ Finalizar actualización 2.Jul.2018

Sin embargo, recomendaría una solución diferente como:

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

la vista de contexto siempre está disponible dentro de la expresión de enlace @{ ... }

Maher Abuthraa
fuente
1
Creo que el código dentro de xml debe evitarse tanto como sea posible: no es comprobable, puede acumularse y no es obvio. Pero estoy de acuerdo en que sobrecargar los atributos estándar puede resultar confuso. Creo que la mejor manera es nombrar el nuevo atributo de manera diferente, en este caso "srcResId", pero aún usar un BindingAdapter
Kirill Starostin
7

Sobre la base de la respuesta de Maher Abuthraa, esto es lo que terminé usando en el XML:

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

La contextvariable está disponible en expresión de enlace sin ninguna importación. Además, no es BindingAdapternecesario personalizarlo . Solo advertencia: el método getDrawablesolo está disponible desde API 21.

Tom Ladek
fuente
6

Para Kotlin, ponga esto en un archivo de utils de nivel superior, no se necesita contexto estático / complementario:

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

Cuanto más puedas hacer con DataBindingAdapter

  • Puede configurar la URL de la imagen , el archivo , el mapa de bits , la matriz de bytes , la identificación dibujable , dibujable y cualquier cosa mediante el enlace de datos.
  • También puede configurar la imagen de error / imágenes de marcador de posición pasando varios parámetros al adaptador de enlace .

Configure cualquiera de estos tipos:

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

Establecer imagen de error / imagen de marcador de posición

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

Probado todos los tipos

CAROLINA DEL SUR

Eso es posible con un solo adaptador de enlace. Simplemente copie este proyecto de método.

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

Razón por la que usé Glide para cargar todos los objetos

Si me preguntas por qué usé Glide para cargar la identificación del recurso / dibujable, en su lugar podría usar imageView.setImageBitmap();o imageView.setImageResource();. Entonces la razón es que

  • Glide es un marco de carga de imágenes eficiente que envuelve la decodificación de medios, la memoria y el almacenamiento en caché de disco. Por lo tanto, no debe preocuparse por imágenes de gran tamaño y caché.
  • Para hacer coherencia al cargar la imagen. Ahora Glide carga todos los tipos de recursos de imágenes.

Si usa Piccaso, Fresso o cualquier otra biblioteca de carga de imágenes, puede realizar cambios en el loadImageWithGlidemétodo .

Khemraj
fuente
`errorImage =" @ {@ drawable / ic_launcher} "`. Esto ni siquiera se compila para mí
Vivek Mishra
@VivekMishra ¿Quizás su ic_launcher esté en mipmap ?, pruebe @ mipmap / ic_launcher.
Khemraj
@VivekMishra ¿Puedes pegar tu registro de errores relevante? ¿Agregó este método en su clase de utilidad vinculante?
Khemraj
**** / error de enlace de datos **** msg: No se puede encontrar el getter para el atributo 'app: src' con el tipo de valor java.lang.String en com.zuowei.circleimageview.CircleImageView. Intenté tanto con Android como con el espacio de nombres de la aplicación y ninguno de los dos funcionó para mí. También reemplacé la vista de imagen predeterminada con circleImageView en el parámetro
Vivek Mishra
También he creado un adaptador de enlace en una clase separada
Vivek Mishra
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}"/>
desarrollador de Android
fuente
3

puedes hacer lo siguiente

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"
Fahad Alotaibi
fuente
2

No soy un experto en Android pero pasé horas intentando descifrar las soluciones existentes. Lo bueno es que comprendí BindingAdapterun poco mejor la idea de vinculación de datos . Por eso, estoy al menos agradecido por las respuestas existentes (aunque muy incompletas). Aquí un desglose completo del enfoque:

También usaré BindingAdapteren este ejemplo. Preparando el 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>

Así que aquí estoy guardando solo las cosas importantes:

  • SomeViewModeles mi ViewModeluso para el enlace de datos. También puede usar una clase que amplíe BaseObservabley use @Bindable. Sin embargo, BindingAdapteren este ejemplo, ¡ no tiene que estar en una clase ViewModelo BaseObservable! ¡Una clase sencilla servirá! Esto se ilustrará más adelante.
  • app:appIconDrawable="@{model.packageName}". Sí ... ¡esto realmente me estaba causando dolores de cabeza! Vamos a analizarlo:
    • app:appIconDrawable: Esto puede ser cualquier cosa app:iCanBeAnything:! De Verdad. ¡También puedes quedarte "android:src"! Sin embargo, tome nota de su elección, ¡la usaremos más adelante!
    • "@ {model.packageName}": si trabajó con enlace de datos , esto le resulta familiar. Más adelante mostraré cómo se usa esto.

Supongamos que usamos esta clase observable simple:

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

Como se prometió, también puede mover el public static void setImageViewDrawable(), a otra clase, por ejemplo, tal vez pueda tener una clase que tenga una colección de BindingAdapters:

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

Otro comentario importante es que en mi Observableclase solía String packageNamepasar información adicional al setImageViewDrawable. También puede elegir, por ejemplo int resourceId, con los getters / setters correspondientes, para lo cual el adaptador se convierte en:

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
fuente
2

Esto funciona para mí. Lo habría agregado a la respuesta de @hqzxzwb como comentario, pero debido a limitaciones de reputación.

Tengo esto en mi modelo de vista

var passport = R.drawable.passport

Luego, en mi xml, tengo

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

Y eso es

Raines
fuente
Está bien, pero olvidas mencionar que debes importar contexto. ¿Podrías actualizar tu respuesta?
pez muerto
1

Usando Fresco (biblioteca de imágenes de Facebook)

 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));
        }
    }
}
최봉재
fuente
0

En su estado de vista o clase de modelo de vista;

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

En su XML;

<androidx.appcompat.widget.AppCompatImageButton
   .
   .
   .
   android:src="@{viewState.getSource(context)}"
Cafer Mert Ceyhan
fuente
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>
Faxriddin Abdullayev
fuente
0

establecer una imagen como esta,

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