Définir l'ID de ressource dessinable dans Android: src pour ImageView à l'aide de la liaison de données dans Android

91

J'essaie de définir l'ID de ressource pouvant être dessiné sur Android: src d'ImageView à l'aide de la liaison de données

Voici mon objet:

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

    // ...
}

Voici ma mise en page:

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

Et enfin, classe d'activité:

// ...

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

    // ...

}

Il n'affiche pas du tout l'image. Qu'est-ce que je fais mal?

BTW, cela fonctionnait parfaitement avec la manière standard:

@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
la source

Réponses:

112

Réponse au 10 novembre 2016

Le commentaire de Splash ci-dessous a souligné qu'il n'est pas nécessaire d'utiliser un type de propriété personnalisé (comme imageResource), nous pouvons à la place créer plusieurs méthodes pour android:srccomme ceci:

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

Ancienne réponse

Vous pouvez toujours essayer d'utiliser un adaptateur:

public class DataBindingAdapters {

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

Vous pouvez ensuite utiliser l'adaptateur dans votre xml comme ceci

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

Assurez-vous de noter que le nom dans le xml correspond à l'annotation BindingAdapter (imageResource)

La classe DataBindingAdapters n'a pas besoin d'être déclarée nulle part en particulier, la mécanique DataBinding la trouvera peu importe (je crois)

Joe Maher
la source
Cela fonctionne en utilisant @BindingAdapter. Mais, la valeur devrait être android:src, non imageResource: @BindingAdapter("android:src"). Merci!
Yuriy Seredyuk
5
@YuriySeredyuk Nooooo! haha s'il vous plaît. Faire cela remplacera chaque utilisation unique de "android: src" dans le XML à travers toute votre application, ce que vous ne voulez certainement PAS faire. Pour que imageResource fonctionne, vous devez changer le xml de l'imageView comme je l'ai fait ci-dessus, la liaison de données déterminera ce qu'il faut y mettre
Joe Maher
1
OK, j'ai compris l'idée. Maintenant en utilisant <ImageView imageResource="@{recipe.imageResource}" />et @BindingAdapter("imageResource"). Je viens de manquer une imageResource="@{recipe.imageResource}"partie de votre code extrait :)
Yuriy Seredyuk
1
N'est-ce pas nécessaire app:imageResource?
NameSpace
1
"Faire cela remplacera chaque utilisation unique de" android: src "dans le XML dans toute votre application" La liaison de données n'est-elle pas assez intelligente pour appliquer uniquement cet attribut à ImageView, car c'est ce qui est défini dans la fonction? Je pense que "android: src" serait préférable .... considérez ce qu'Android lui-même fait pour les adaptateurs de liaison ImageView: android.googlesource.com/platform/frameworks/data-binding
Splash
41

Il n'y a aucun besoin de coutume BindingAdapter. Juste utiliser

app:imageResource="@{yourResId}"

et cela fonctionnera très bien.

Vérifiez ceci pour savoir comment cela fonctionne.

hqzxzwb
la source
2
cela devrait avoir plus de votes positifs car c'est une excellente réponse vers 2020
JohnnyLambada
certainement, la meilleure et la plus simple réponse
luckyhandler
Cela semble être la réponse la meilleure et la plus appropriée à la fin de 2020
mcy le
Je regarde la ImageViewclasse et suis la setImageResourceméthode, il semble que finalement est résolu surresolveUri et qu'il n'y ait sinon aucune validation. Cela fonctionnerait donc car Intje me demande ce qui pourrait arriver si Int?. Lorsque les liaisons sont exécutées, par exemple, si quelque chose d'autre appelle, executePendingBindingsalors non nullable est défini par défaut sur zéro, nullables sur null.
cutiko
25

définir:

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

utilisation:

<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
la source
où puis-je ajouter cette méthode
myatmins
support ajoutez-le dans n'importe quelle classe, peut-être que vous pouvez créer un ImageDataBindingAdapter.class.
qinmiao
12

Ne remplacez jamais les attributs standard du SDK lorsque vous créez les vôtres @BindingAdapter !

Ce n'est pas une bonne approche pour de nombreuses raisons telles que: cela empêchera d'obtenir les avantages de nouveaux correctifs sur la mise à jour du SDK Android sur cet attribut. En outre, cela pourrait dérouter les développeurs et sûrement compliqué pour la réutilisabilité (car il ne devrait pas être remplacé)

vous pouvez utiliser différents espaces de noms comme:

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

ou

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

------ démarrer la mise à jour 2.juil.2018

Il n'est pas recommandé d'utiliser l'espace de noms, il est donc préférable de s'appuyer sur un préfixe ou un nom différent comme:

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

ou

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

------ mettre fin à la mise à jour du 2 juillet 2018

Cependant, je recommanderais une solution différente comme:

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

la vue contextuelle est toujours disponible dans l'expression de liaison @{ ... }

Maher Abuthraa
la source
1
Je pense que le code à l'intérieur de xml doit être évité autant que possible - il n'est pas testable, il peut s'accumuler et ce n'est pas évident. Mais je suis d'accord que la surcharge des attributs standard peut être déroutante. Je pense que le meilleur moyen est de nommer le nouvel attribut différemment, dans ce cas "srcResId", mais toujours d'utiliser un BindingAdapter
Kirill Starostin
7

Sur la base de la réponse de Maher Abuthraa, voici ce que j'ai fini par utiliser dans le XML:

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

La contextvariable est disponible dans l'expression de liaison sans aucune importation. En outre, aucune coutume n'est BindingAdapternécessaire. Seul bémol: la méthode getDrawablen'est disponible que depuis l'API 21.

Tom Ladek
la source
6

Pour Kotlin, placez ceci dans un fichier utils de niveau supérieur, aucun contexte statique / compagnon n'est nécessaire:

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

Plus vous pouvez faire avec DataBindingAdapter

  • Vous pouvez définir l' URL de l'image , le fichier , le bitmap , le tableau d'octets , le dessin , l' ID de dessin n'importe quoi par liaison de données.
  • Vous pouvez également définir une image d'erreur / des images d'espace réservé en passant plusieurs paramètres à l'adaptateur de liaison .

Définissez l'un de ces 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`}"

Définir l'image d'erreur / l'image d'espace réservé

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

Testé tous les types

SC

Cela devient donc possible avec un seul adaptateur de liaison. Copiez simplement ce projet de méthode.

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

Raison pour laquelle j'ai utilisé Glide pour charger tous les objets

Si vous me demandez pourquoi j'ai utilisé Glide pour charger l'ID drawable / resource, je pourrais plutôt utiliser imageView.setImageBitmap();ou imageView.setImageResource();. Donc la raison est que

  • Glide est une infrastructure de chargement d'image efficace qui englobe le décodage multimédia, la mémoire et la mise en cache du disque. Vous n'avez donc pas à vous soucier des images de grande taille et du cache.
  • Pour assurer la cohérence lors du chargement de l'image. Désormais, tous les types de ressources image sont chargés par Glide.

Si vous utilisez Piccaso, Fresso ou toute autre bibliothèque de chargement d'images, vous pouvez modifier la loadImageWithGlideméthode .

Khemraj
la source
`errorImage =" @ {@ drawable / ic_launcher} "`. Cette chose ne compile même pas pour moi
Vivek Mishra
@VivekMishra Peut-être que votre ic_launcher est dans mipmap?, Essayez @ mipmap / ic_launcher.
Khemraj
@VivekMishra Pouvez-vous coller votre journal d'erreurs pertinent? Avez-vous ajouté cette méthode dans votre classe util de liaison?
Khemraj
**** / erreur de liaison de données **** msg: Impossible de trouver le getter pour l'attribut 'app: src' avec le type de valeur java.lang.String sur com.zuowei.circleimageview.CircleImageView. J'ai essayé avec les espaces de noms Android et App et les deux n'ont pas fonctionné pour moi. J'ai également remplacé imageview par défaut par circleImageView dans le paramètre
Vivek Mishra
J'ai également créé un adaptateur de liaison dans une classe distincte
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}"/>
développeur_android
la source
3

vous pouvez faire ce qui suit

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

Je ne suis pas un expert d'Android mais j'ai passé des heures à essayer de déchiffrer les solutions existantes. La bonne chose est que j'ai saisi toute l'idée de la liaison de données en utilisantBindingAdapter un peu mieux. Pour cela, je suis au moins reconnaissant pour les réponses existantes (bien que très incomplètes). Voici un aperçu complet de l'approche:

J'utiliserai également le BindingAdapterdans cet exemple. Préparer le 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>

Donc ici je ne garde que les choses importantes:

  • SomeViewModelest mon ViewModelque j'utilise pour la liaison de données. Vous pouvez également utiliser une classe qui étend BaseObservableet utilise @Bindable. Cependant, BindingAdapterdans cet exemple, n'a pas besoin d' être dans unViewModelBaseObservable classe ou ! Une classe simple fera l'affaire! Cela sera illustré plus tard.
  • app:appIconDrawable="@{model.packageName}". Oui ... cela me causait vraiment des maux de tête! Décomposons-le:
    • app:appIconDrawable: Cela peut être n'importe quoi app:iCanBeAnything:! Vraiment. Vous pouvez également garder"android:src" ! Cependant, prenez note de votre choix, nous l'utiliserons plus tard!
    • "@ {model.packageName}": si vous avez travaillé avec la liaison de données , cela vous est familier. Je montrerai comment cela est utilisé plus tard.

Supposons que nous utilisons cette simple classe Observable:

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

Comme promis, vous pouvez également déplacer le public static void setImageViewDrawable(), vers une autre classe, par exemple vous pouvez peut-être avoir une classe qui a une collection de BindingAdapters:

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

Une autre remarque importante est que dans ma Observableclasse, je String packageNametransmettais des informations supplémentaires au setImageViewDrawable. Vous pouvez également choisir par exemple int resourceId, avec les getters / setters correspondants, pour lesquels l'adaptateur devient:

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
la source
2

Ce travail pour moi. Je l'aurais ajouté à la réponse @hqzxzwb en commentaire, mais en raison de limitations de réputation.

J'ai ceci dans ma vue Modèle

var passport = R.drawable.passport

Puis dans mon xml, j'ai

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

Et c'est tout

Raines
la source
OK mais vous oubliez de mentionner que vous devez importer le contexte. Pouvez-vous mettre à jour votre réponse?
deadfish
1

Utilisation de Fresco (bibliothèque d'images 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));
        }
    }
}
최봉재
la source
0

Dans votre état d'affichage ou dans votre classe de modèle de vue;

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

Dans votre XML;

<androidx.appcompat.widget.AppCompatImageButton
   .
   .
   .
   android:src="@{viewState.getSource(context)}"
Café Mert Ceyhan
la source
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
la source
0

définir l'image comme celle-ci,

  <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
la source