Встановіть ідентифікований ідентифікатор ресурсу в android: src для ImageView, використовуючи прив'язку даних в Android


91

Я намагаюся встановити для ідентифікатора ресурсу, який можна малювати, андроїд: src ImageView за допомогою прив'язки даних

Ось мій об’єкт:

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

}

Відповіді:


112

Відповідь станом на 10 листопада 2016 року

Коментар Splash нижче наголосив на тому, що не потрібно використовувати власний тип властивості (like imageResource), ми можемо замість цього створити кілька методів для android:srclike:

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 знайде це незалежно (я вважаю)


Це працює за допомогою @BindingAdapter. Але, значення повинно бути android:src, а НЕ imageResource: @BindingAdapter("android:src"). Дякую!
Юрій Середюк

5
@YuriySeredyuk Nooooo! ха-ха, будь ласка. Це перекриє кожне використання "android: src" у xml у всій програмі, що ви точно НЕ хочете робити. Щоб imageResource працював, вам потрібно змінити xml для imageView, як я це робив вище, прив'язка даних вирішить, що туди помістити
Джо Махер,

1
Добре, я зрозумів ідею. Зараз використовується <ImageView imageResource="@{recipe.imageResource}" />і @BindingAdapter("imageResource"). Я щойно пропустив imageResource="@{recipe.imageResource}"частину вашого фрагмента коду :)
Юрій Середюк

1
Хіба це не повинно бути app:imageResource?
NameSpace

1
"Це перекриє кожне використання" android: src "у xml у всьому додатку." Чи недостатньо розумна прив'язка даних, щоб застосувати цей атрибут до ImageView, оскільки саме це визначено у функції? Я думаю, що "android: src" буде кращим .... розглянемо, що саме робить Android для адаптерів прив'язки ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash

40

Спеціальність BindingAdapterвзагалі не потрібна . Просто використовуйте

app:imageResource="@{yourResId}"

і це буде працювати нормально.

Перевірте це за те, як він працює.


2
це повинно мати більше голосів, оскільки це чудова відповідь близько 2020 року
JohnnyLambada

безумовно, найкраща і найпростіша відповідь
luckyhandler

Це виглядає як найкращий і найбільш прийнятних відповідей за станом на кінець 2020 року
MCy

Я переглядаю ImageViewклас і слідую setImageResourceметоду, здається, що врешті-решт це вирішено, resolveUriі є, якщо не нульова перевірка. Отже, це спрацювало б. IntЦікаво, що може статися, якщо Int?. Коли прив'язки виконуються, на прикладі, якщо щось інше викликає, executePendingBindingsто не має значення NULL за замовчуванням дорівнює нулю, а значення NULL - до нуля.
cutiko

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

де я можу додати цей метод
myatmins

підтримка додайте його в будь-який клас, можливо, ви можете створити ImageDataBindingAdapter.class.
qinmiao

12

Ніколи не перевизначайте стандартні атрибути SDK, коли ви створюєте власні @BindingAdapter!

Це невдалий підхід з багатьох причин, таких як: він не дозволить отримати переваги нових виправлень в оновлення Android SDK для цього атрибута. Крім того, це може заплутати розробників і, безсумнівно, хитруватиме для повторного використання (оскільки воно не передбачене для заміни)

Ви можете використовувати інший простір імен, наприклад:

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

або

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

------ початок оновлення 2. липня 2018 р

Не рекомендується використовувати простір імен, тому краще покладатися на префікс або інше ім'я, як:

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

або

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

------ кінець оновлення 2. липня 2018 р

Однак я б рекомендував інше рішення, як:

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

контекстний вигляд завжди доступний у виразі прив'язки @{ ... }


1
Я думаю, що коду всередині xml слід уникати якомога більше - він не перевіряється, він може накопичуватися і це не очевидно. Але я згоден, що перевантаження стандартних атрибутів може заплутати. Я думаю, що найкращий спосіб - назвати новий атрибут по-іншому, в даному випадку "srcResId", але все-таки використовувати BindingAdapter
Кирило Старостін

7

Спираючись на відповідь Махера Абутраа, ось що я в підсумку використав у XML:

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

contextМінлива доступна в зв'язуванні вираження без імпорту. Крім того, ніяких звичаїв не BindingAdapterпотрібно. Тільки застереження: метод getDrawableдоступний лише з API 21.


6

Для Kotlin помістіть це у файл утилів верхнього рівня, не потрібен статичний / супутній контекст:

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

5

Тим більше ви можете зробити 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`}"

Встановити зображення помилки / зображення заповнювача

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 для завантаження ідентифікатора ресурсу, що малюється / ресурсу, замість цього я міг використати imageView.setImageBitmap();або imageView.setImageResource();. Тож причина в тому

  • Glide - це ефективний фреймворк для завантаження зображень, який охоплює декодування носіїв, пам’ять та кешування диска. Тому вам не слід турбуватися про великі розміри зображень та кеш-пам’яті.
  • Для забезпечення послідовності під час завантаження зображення. Тепер усі типи графічних ресурсів завантажуються Glide.

Якщо ви використовуєте Piccaso, Fresso або будь-яку іншу бібліотеку для завантаження зображень, ви можете внести зміни в loadImageWithGlideметод .


`errorImage =" @ {@ drawable / ic_launcher} "`. Ця річ навіть не складається для мене
Vivek Mishra

@VivekMishra Можливо, ваш ic_launcher знаходиться у mipmap ?, спробуйте @ mipmap / ic_launcher.
Khemraj

@VivekMishra Чи можете ви вставити відповідний журнал помилок? Ви додали цей метод у свій клас прив'язки util?
Хемрай

**** / помилка прив'язки даних **** повідомлення: Не вдається знайти геттер для атрибута 'app: src' із типом значення java.lang.String на com.zuowei.circleimageview.CircleImageView. Я спробував як з android, так і з простором імен додатків, і обидва вони не працювали для мене. Я також замінив перегляд зображень за замовчуванням на circleImageView у параметрі
Vivek Mishra

Також я створив перехідник для прив'язки в окремому класі
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}"/>


2

Я не фахівець з Android, але годинами намагався розшифрувати існуючі рішення. Гарна річ у тому, що я зрозумів всю ідею прив'язки даних, використовуючи BindingAdapterтрохи краще. За це я принаймні вдячний за існуючі відповіді (хоча і дуже неповні). Ось повна розбивка підходу:

Я також використаю BindingAdapterв цьому прикладі. Підготовка 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>

Отже, тут я зберігаю лише важливі речі:

  • SomeViewModelце ViewModelя використовую для прив'язки даних. Ви також можете використовувати клас, який розширює BaseObservableта використовує @Bindable. Однак BindingAdapterу цьому прикладі не обов’язково бути в класі ViewModelабо BaseObservableкласі! Звичайний клас підійде! Це буде проілюстровано пізніше.
  • app:appIconDrawable="@{model.packageName}". Так ... це справді викликало у мене головний біль! Давайте розберемо це:
    • app:appIconDrawable: Це може бути що завгодно app:iCanBeAnything:! Дійсно. Ви також можете тримати "android:src"! Однак візьміть до відома свій вибір, ми використаємо його пізніше!
    • "@ {model.packageName}": Якщо ви працювали з прив'язкою даних , це знайоме. Я покажу, як це використовується пізніше.

Припустимо, ми використовуємо цей простий клас 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));
   }
}

Як і обіцяли, ви також можете перенести public static void setImageViewDrawable()файл до іншого класу, наприклад, можливо, у вас може бути клас, який має колекцію BindingAdapters:

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

Іншим важливим зауваженням є те, що у своєму Observableкласі я раніше String packageNameпередавав додаткову інформацію до setImageViewDrawable. Ви також можете вибрати, наприклад int resourceId, з відповідними геттерами / сеттерами, для яких адаптером стає:

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

2

Ця робота для мене. я б додав його до відповіді @hqzxzwb як коментар, але через обмеження репутації.

У мене така, на мій погляд, модель

var passport = R.drawable.passport

Тоді у своєму xml я маю

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

І це все


Добре, але ви забуваєте згадати, що потрібно імпортувати контекст. Не могли б ви оновити свою відповідь?
deadfish

1

Використання фрески (бібліотека зображень 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));
        }
    }
}

0

У вашому стані перегляду або класі моделі перегляду;

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

У вашому XML;

<androidx.appcompat.widget.AppCompatImageButton
   .
   .
   .
   android:src="@{viewState.getSource(context)}"

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>

0

встановити таке зображення,

  <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" />
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.