Setel ID sumber daya yang dapat digambar di android: src untuk ImageView menggunakan data binding di Android


91

Saya mencoba menyetel ID sumber daya yang dapat digambar ke android: src dari ImageView menggunakan data binding

Inilah objek saya:

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

    // ...
}

Inilah tata letak saya:

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

Dan terakhir, kelas aktivitas:

// ...

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

    // ...

}

Itu tidak menampilkan gambar sama sekali. Apa yang saya lakukan salah?

BTW, itu bekerja sempurna dengan cara standar:

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

}

Jawaban:


112

Jawaban per 10 November 2016

Komentar Splash di bawah ini telah menyoroti bahwa tidak perlu menggunakan tipe properti khusus (suka imageResource), sebagai gantinya kita dapat membuat beberapa metode untuk android:srcseperti itu:

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

Jawaban Lama

Anda selalu dapat mencoba menggunakan adaptor:

public class DataBindingAdapters {

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

Anda kemudian dapat menggunakan adaptor di xml Anda seperti itu

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

Pastikan untuk memperhatikan bahwa nama dalam xml cocok dengan anotasi BindingAdapter (imageResource)

Kelas DataBindingAdapters tidak perlu dideklarasikan di mana pun secara khusus, mekanik DataBinding akan menemukannya tidak masalah (saya percaya)


Ia bekerja dengan menggunakan @BindingAdapter. Tapi, nilai harus android:src, tidak imageResource: @BindingAdapter("android:src"). Terima kasih!
Yuriy Seredyuk

5
@YuriySeredyuk Tidaaaaak! haha tolong. Melakukan itu akan menimpa setiap penggunaan "android: src" dalam xml di seluruh aplikasi Anda yang pasti TIDAK ingin Anda lakukan. Agar imageResource berfungsi, Anda harus mengubah xml untuk imageView seperti yang telah saya lakukan di atas, penyatuan data akan menentukan apa yang harus diletakkan di sana
Joe Maher

1
Oke, saya mengerti ide. Sekarang menggunakan <ImageView imageResource="@{recipe.imageResource}" />dan @BindingAdapter("imageResource"). Saya baru saja melewatkan imageResource="@{recipe.imageResource}"bagian dari potongan kode Anda :)
Yuriy Seredyuk

1
Bukankah ini perlu app:imageResource?
NameSpace

1
"Melakukan hal itu akan menimpa setiap penggunaan" android: src "dalam xml di seluruh aplikasi Anda" Bukankah penyatuan data cukup cerdas untuk hanya menerapkan atribut itu ke ImageView, karena itulah yang didefinisikan dalam fungsi? Saya pikir "android: src" akan lebih disukai .... pertimbangkan apa yang Android sendiri lakukan untuk adaptor pengikat ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash

41

Tidak perlu adat BindingAdaptersama sekali. Gunakan saja

app:imageResource="@{yourResId}"

dan itu akan bekerja dengan baik.

Periksa ini untuk mengetahui cara kerjanya.


2
ini seharusnya memiliki lebih banyak suara positif karena ini adalah jawaban yang bagus sekitar tahun 2020
JohnnyLambada

pasti, jawaban terbaik dan paling sederhana
luckyhandler

Sepertinya ini jawaban terbaik dan paling tepat pada akhir 2020
mcy

Saya melihat ImageViewkelas dan mengikuti setImageResourcemetode, tampaknya akhirnya diselesaikan resolveUridan jika tidak ada validasi nol. Jadi itu akan berhasil karena Intsaya bertanya-tanya apa yang bisa terjadi jika Int?. Ketika binding dijalankan, sebagai contoh jika sesuatu memanggil executePendingBindingsmaka tidak nullable secara default menjadi nol, nullables menjadi null.
cutiko

25

menetapkan:

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

menggunakan:

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

di mana saya menambahkan metode itu
myatmins

dukungan menambahkannya di kelas apa pun, mungkin Anda dapat membuat ImageDataBindingAdapter.class.
qinmiao

12

Jangan pernah mengganti atribut SDK standar saat Anda membuatnya sendiri @BindingAdapter!

Ini bukan pendekatan yang baik karena banyak alasan seperti: ini akan mencegah perolehan manfaat dari perbaikan baru pada pembaruan SDK Android pada atribut itu. Juga mungkin membingungkan pengembang dan pasti rumit untuk digunakan kembali (karena tidak diharapkan diganti)

Anda dapat menggunakan namespace yang berbeda seperti:

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

atau

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

------ mulai Pembaruan 2. Juli 2018

Namespace tidak disarankan untuk digunakan, jadi lebih baik mengandalkan awalan atau nama yang berbeda seperti:

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

atau

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

------ akhiri Pembaruan 2. Juli 2018

Namun, saya akan merekomendasikan solusi yang berbeda sebagai:

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

tampilan konteks selalu tersedia di dalam ekspresi binding @{ ... }


1
Saya pikir kode di dalam xml harus dihindari sebisa mungkin - tidak dapat diuji, dapat menumpuk dan tidak jelas. Tetapi saya setuju bahwa atribut standar yang berlebihan dapat membingungkan. Saya pikir cara terbaik adalah memberi nama atribut baru secara berbeda, dalam hal ini "srcResId", tetapi masih menggunakan BindingAdapter
Kirill Starostin

7

Berdasarkan jawaban dari Maher Abuthraa, inilah yang akhirnya saya gunakan dalam XML:

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

The contextvariabel tersedia dalam mengikat ekspresi tanpa impor apapun. Juga, tidak BindingAdapterperlu kebiasaan . Hanya peringatan: metode getDrawableini hanya tersedia sejak API 21.


6

Untuk Kotlin, letakkan ini ke file utils tingkat atas, tidak diperlukan konteks statis / pengiring:

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

5

Semakin banyak yang dapat Anda lakukan DataBindingAdapter

Tetapkan salah satu dari jenis ini:

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

Setel Gambar kesalahan / Gambar placeholder

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

Menguji semua tipe

SC

Sehingga menjadi mungkin dengan adaptor pengikat tunggal. Cukup salin proyek metode ini.

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

Alasan saya menggunakan Glide untuk memuat semua objek

Jika Anda bertanya mengapa saya menggunakan Glide untuk memuat id drawable / resource, sebagai gantinya saya dapat menggunakan imageView.setImageBitmap();atau imageView.setImageResource();. Jadi alasannya adalah itu

  • Glide adalah kerangka kerja pemuatan gambar yang efisien yang membungkus decoding media, memori, dan cache disk. Jadi Anda tidak perlu khawatir dengan gambar dan cache berukuran besar.
  • Untuk membuat konsistensi saat memuat gambar. Sekarang semua jenis sumber gambar dimuat oleh Glide.

Jika Anda menggunakan Piccaso, Fresso atau pustaka pemuatan gambar lainnya, Anda dapat membuat perubahan loadImageWithGlidemetode .


`errorImage =" @ {@ drawable / ic_launcher} "`. Hal ini bahkan tidak dapat dikompilasi untuk saya
Vivek Mishra

@VivekMishra Mungkin ic_launcher Anda ada di mipmap ?, coba @ mipmap / ic_launcher.
Khemraj

@VivekMishra Dapatkah Anda menempelkan log kesalahan yang relevan? Apakah Anda menambahkan metode ini di kelas util binding Anda?
Khemraj

**** / data binding error **** msg: Tidak dapat menemukan pengambil atribut 'app: src' dengan tipe nilai java.lang.String di com.zuowei.circleimageview.CircleImageView. Saya mencoba dengan android serta namespace aplikasi dan keduanya tidak berhasil untuk saya. Saya juga mengganti imageview default dengan circleImageView di parameter
Vivek Mishra

Juga saya telah membuat Adaptor pengikat di kelas terpisah
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}"/>

3

Anda dapat melakukan hal berikut

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"

2

Saya bukan ahli di Android tetapi saya menghabiskan waktu berjam-jam mencoba menguraikan solusi yang ada. Hal baiknya adalah saya memahami seluruh gagasan tentang data binding menggunakanBindingAdapter sedikit lebih baik. Untuk itu, saya setidaknya berterima kasih atas jawaban yang ada (meski sangat tidak lengkap). Berikut rincian lengkap dari pendekatan tersebut:

Saya juga akan menggunakan BindingAdapterdalam contoh ini. Mempersiapkan 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>

Jadi di sini saya hanya menyimpan yang penting:

  • SomeViewModeladalah saya yang ViewModelsaya gunakan untuk pengikatan data. Anda juga dapat menggunakan kelas yang diperluas BaseObservabledan digunakan @Bindable. Namun, BindingAdapterdalam contoh ini, tidak harus berada di kelas ViewModelatau BaseObservable! Kelas biasa sudah cukup! Ini akan diilustrasikan nanti.
  • app:appIconDrawable="@{model.packageName}". Ya ... ini benar-benar membuatku pusing! Mari kita hancurkan:
    • app:appIconDrawable: Ini bisa apa saja: app:iCanBeAnything! Betulkah. Anda juga bisa menyimpannya "android:src"! Namun, perhatikan pilihan Anda, kami akan menggunakannya nanti!
    • "@ {model.packageName}": Jika Anda bekerja dengan data binding , ini familiar. Saya akan menunjukkan bagaimana ini digunakan nanti.

Mari kita asumsikan kita menggunakan kelas Observable sederhana ini:

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

Seperti yang dijanjikan, Anda juga dapat memindahkan public static void setImageViewDrawable(), ke kelas lain, misalnya mungkin Anda dapat memiliki kelas yang memiliki koleksi BindingAdapters:

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

Komentar penting lainnya adalah bahwa di Observablekelas saya, saya biasa String packageNamememberikan info tambahan ke setImageViewDrawable. Anda juga dapat memilih misalnya int resourceId, dengan getter / setter yang sesuai, yang adaptornya menjadi:

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

Ini bekerja untuk saya. saya akan menambahkannya ke jawaban @hqzxzwb sebagai komentar tetapi karena batasan reputasi.

Saya memiliki ini dalam Model pandangan saya

var passport = R.drawable.passport

Kemudian di xml saya, saya punya

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

Dan hanya itu


Oke, tetapi Anda lupa menyebutkan bahwa Anda harus mengimpor konteks. Bisakah Anda memperbarui jawaban Anda?
deadfish

1

Menggunakan Fresco (perpustakaan gambar 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

Dalam status tampilan atau kelas model tampilan Anda;

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

Dalam XML Anda;

<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

atur gambar seperti ini,

  <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" />
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.