J'essaie de définir l'ID de ressource pouvant être dessiné sur Android: src de ImageView en utilisant 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 d'image du tout. Qu'est-ce que je fais mal?
BTW, cela fonctionnait parfaitement avec la méthode 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);
}
Réponse en date du 10 nov 2016
Le commentaire de Splash ci-dessous a mis en évidence qu'il n'est pas nécessaire d'utiliser un type de propriété personnalisé (comme imageResource
), nous pouvons créer plusieurs méthodes pour Android:src
comme suit:
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
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 fichier xml comme si
<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 fichier xml correspond à l'annotation BindingAdapter (imageResource)
La classe DataBindingAdapters n'a pas besoin d'être déclarée nulle part en particulier, les mécaniciens de DataBinding le trouveront peu importe (je crois)
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}"/>
Ne remplacez jamais les attributs SDK standard lorsque vous créez votre propre @BindingAdapter
!
Ce n'est pas une bonne approche pour de nombreuses raisons, telles que: En outre, cela pourrait dérouter les développeurs et rendre la possibilité de réutilisation plus difficile (car il n’est pas exclu qu’il soit 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
L’utilisation de l’espace de noms n’est pas recommandée. Il est donc préférable de s’appuyer sur un préfixe ou un nom différent, comme suit:
app:custom_src="@{recipe.imageResource}"
ou
app:customSrc="@{recipe.imageResource}"
------ fin de la mise à jour 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 @{ ... }
Pour Kotlin mettez 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)
}
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}"/>
DataBindingAdapter
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`}"
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`}"
/>
Cela devient donc possible avec un adaptateur de liaison unique. Il suffit de copier 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);
}
}
Si vous me demandez pourquoi j'ai utilisé Glide pour charger l'identifiant de ressource/dessinable, je pourrais plutôt utiliser imageView.setImageBitmap();
ou imageView.setImageResource();
. Donc la raison est que
Si vous utilisez Piccaso, Fresso ou toute autre bibliothèque de chargement d'images, vous pouvez modifier la méthode loadImageWithGlide
.
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));
}
}
}
Je ne suis pas un expert sur Android, mais j'ai passé des heures à essayer de déchiffrer les solutions existantes. La bonne chose est que j'ai saisi l'idée de la liaison de données en utilisant BindingAdapter
un peu mieux. Pour cela, je suis au moins reconnaissant des réponses existantes (bien que très incomplètes). Voici un aperçu complet de l'approche:
Je vais aussi utiliser la BindingAdapter
dans cet exemple. Préparation de la 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:
SomeViewModel
est ma ViewModel
que j'utilise pour la liaison de données. Vous pouvez également utiliser une classe qui étend la variable BaseObservable
et utiliser @Bindable
. Cependant, la variable BindingAdapter
dans cet exemple, ne doit pas nécessairement être être dans une classe ViewModel
ou BaseObservable
! Une classe ordinaire fera l'affaire! Ceci sera illustré plus tard.app:appIconDrawable="@{model.packageName}"
. Oui ... ça me causait vraiment des maux de tête! Décomposons-le: app:appIconDrawable
: Cela peut être n'importe quoi: app:iCanBeAnything
! Vraiment. Vous pouvez aussi garder "Android:src"
! Cependant, prenez une note sur votre choix, nous l'utiliserons plus tard!Supposons que nous utilisions 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 aligned, that's it! :)
//
// The name of the fuction, 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 la public static void setImageViewDrawable()
, dans une autre classe, par exemple. vous pouvez peut-être 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 classe Observable
, j'ai utilisé String packageName
pour transmettre des informations supplémentaires à la setImageViewDrawable
. Vous pouvez également choisir par exemple int resourceId
, avec les getters/setters correspondants, pour lequel 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);
}
}
vous pouvez faire ce qui suit
Android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"