Đặt ID tài nguyên có thể vẽ trong android: src cho ImageView sử dụng liên kết dữ liệu trong Android


91

Tôi đang cố đặt ID tài nguyên có thể vẽ thành android: src of ImageView bằng cách sử dụng liên kết dữ liệu

Đây là đối tượng của tôi:

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

    // ...
}

Đây là bố cục của tôi:

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

Và cuối cùng, lớp hoạt động:

// ...

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

    // ...

}

Nó hoàn toàn không hiển thị hình ảnh. Tôi đang làm gì sai?

BTW, nó đã hoạt động hoàn hảo với cách tiêu chuẩn:

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

}

Câu trả lời:


112

Câu trả lời kể từ ngày 10 tháng 11 năm 2016

Nhận xét của Splash bên dưới đã nhấn mạnh rằng không cần thiết phải sử dụng loại thuộc tính tùy chỉnh (like imageResource), thay vào đó chúng ta có thể tạo nhiều phương thức cho android:srcnhư vậy:

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

Câu trả lời cũ

Bạn luôn có thể cố gắng sử dụng bộ điều hợp:

public class DataBindingAdapters {

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

Sau đó, bạn có thể sử dụng bộ điều hợp trong xml của mình như vậy

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

Hãy chắc chắn nhận thấy rằng tên trong xml khớp với chú thích BindingAdapter (imageResource)

Lớp DataBindingAdapters không cần phải được khai báo ở bất kỳ đâu cụ thể, cơ chế DataBinding sẽ tìm thấy nó không có vấn đề gì (tôi tin)


Nó hoạt động bằng cách sử dụng @BindingAdapter. Nhưng, giá trị nên android:src, không imageResource: @BindingAdapter("android:src"). Cảm ơn bạn!
Yuriy Seredyuk

5
@YuriySeredyuk Nooooo! haha làm ơn. Làm điều đó sẽ ghi đè mọi lần sử dụng "android: src" trong xml trên toàn bộ ứng dụng của bạn mà bạn chắc chắn KHÔNG muốn làm. Để có được imageResource để làm việc bạn phải thay đổi xml cho IMAGExem như tôi đã làm ở trên, liên kết dữ liệu sẽ làm việc ra những gì để đặt ở đó
Joe Maher

1
OK, tôi đã hiểu ý tưởng. Hiện đang sử dụng <ImageView imageResource="@{recipe.imageResource}" />@BindingAdapter("imageResource"). Tôi chỉ bỏ lỡ imageResource="@{recipe.imageResource}"một phần từ mã của bạn snipped :)
Yuriy Seredyuk

1
Điều này không cần phải được app:imageResource?
NameSpace

1
"Làm điều đó sẽ ghi đè mọi lần sử dụng" android: src "trong xml trên toàn bộ ứng dụng của bạn" Không phải việc chia dữ liệu đủ thông minh để chỉ áp dụng thuộc tính đó cho ImageView, vì đó là gì được định nghĩa trong hàm? Tôi nghĩ rằng "android: src" sẽ phù hợp hơn .... hãy xem xét bản thân Android làm gì cho các bộ điều hợp ràng buộc ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash

40

Không cần một tùy chỉnh nào BindingAdaptercả. Chỉ dùng

app:imageResource="@{yourResId}"

và nó sẽ hoạt động tốt.

Kiểm tra điều này để biết nó hoạt động như thế nào.


2
điều này sẽ có nhiều phiếu tán thành hơn vì đó là một câu trả lời tuyệt vời vào khoảng năm 2020
JohnnyLambada

chắc chắn, câu trả lời tốt nhất và đơn giản nhất
luckyhandler

Đây có vẻ là câu trả lời tốt nhất và phù hợp nhất vào cuối năm 2020
mcy 22/09

Tôi đang xem xét ImageViewlớp và làm theo setImageResourcephương pháp, có vẻ như cuối cùng đã được giải quyết resolveUrivà có nếu không phải là xác thực bằng không. Vì vậy, điều đó sẽ làm việc cho Inttôi tự hỏi điều gì có thể xảy ra nếu Int?. Khi các ràng buộc được thực thi, chẳng hạn nếu một cái gì đó khác gọi executePendingBindingsthì không thể nullable được mặc định là 0, nullable thành null.
cutiko

25

định nghĩa:

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

sử dụng:

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

tôi thêm phương thức đó ở đâu
myatmins

hỗ trợ thêm nó vào bất kỳ lớp nào, có thể bạn có thể tạo một lớp ImageDataBindingAdapter.
qinmiao

12

Không bao giờ ghi đè các thuộc tính SDK tiêu chuẩn khi bạn tạo thuộc tính của riêng mình @BindingAdapter!

Đây không phải là một cách tiếp cận tốt vì nhiều lý do như: nó sẽ ngăn cản việc thu được lợi ích từ các bản sửa lỗi mới trên bản cập nhật Android SDK trên thuộc tính đó. Ngoài ra, nó có thể gây nhầm lẫn cho các nhà phát triển và chắc chắn khó sử dụng lại (vì nó không được giải thích để bị ghi đè)

bạn có thể sử dụng không gian tên khác nhau như:

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

hoặc là

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

------ bắt đầu Cập nhật 2.Jul.2018

Không gian tên không được khuyến khích sử dụng, vì vậy tốt hơn nên dựa vào tiền tố hoặc tên khác như:

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

hoặc là

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

------ kết thúc Cập nhật 2.Jul.2018

Tuy nhiên, tôi muốn giới thiệu giải pháp khác như:

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

chế độ xem ngữ cảnh luôn có sẵn bên trong biểu thức ràng buộc @{ ... }


1
Tôi nghĩ rằng mã bên trong xml nên được tránh càng nhiều càng tốt - nó không thể kiểm tra được, nó có thể chồng chất và không rõ ràng. Nhưng tôi đồng ý rằng việc nạp chồng các thuộc tính tiêu chuẩn có thể gây nhầm lẫn. Tôi nghĩ rằng cách tốt nhất là đặt tên thuộc tính mới khác nhau, trong trường hợp này "srcResId", nhưng vẫn sử dụng một BindingAdapter
Kirill Starostin

7

Dựa trên câu trả lời từ Maher Abuthraa, đây là những gì tôi đã sử dụng trong XML:

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

Các contextbiến có sẵn trong ràng buộc biểu hiện mà không cần bất kỳ nhập khẩu. Ngoài ra, không BindingAdaptercần tùy chỉnh . Chỉ cảnh báo: phương pháp getDrawablenày chỉ có sẵn kể từ API 21.


6

Đối với Kotlin, hãy đặt tệp này vào tệp utils cấp cao nhất, không cần ngữ cảnh tĩnh / đồng hành:

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

5

Bạn càng có thể làm với DataBindingAdapter

Đặt bất kỳ loại nào sau đây:

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

Đặt hình ảnh lỗi / hình ảnh trình giữ chỗ

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 tất cả các kiểu

SC

Vì vậy, điều đó trở nên khả thi với bộ điều hợp liên kết đơn. Chỉ cần sao chép dự án phương pháp này.

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

Lý do tôi sử dụng Glide để tải tất cả các đối tượng

Nếu bạn hỏi tôi tại sao tôi sử dụng Glide để tải drawable / resource id, thay vào đó tôi có thể sử dụng imageView.setImageBitmap();or imageView.setImageResource();. Vậy lý do là

  • Glide là một khung tải hình ảnh hiệu quả bao gồm giải mã phương tiện, bộ nhớ và bộ nhớ đệm đĩa. Vì vậy, bạn không cần phải lo lắng về hình ảnh kích thước lớn và bộ nhớ cache.
  • Để tạo sự nhất quán trong khi tải hình ảnh. Bây giờ tất cả các loại tài nguyên hình ảnh đều được tải bằng Glide.

Nếu bạn sử dụng Piccaso, Fresso hoặc bất kỳ thư viện tải hình ảnh nào khác, bạn có thể thực hiện các thay đổi trong loadImageWithGlidephương pháp .


`errorImage =" @ {@ drawable / ic_launcher} "`. Điều này thậm chí không biên dịch đối với tôi
Vivek Mishra

@VivekMishra Có lẽ ic_launcher của bạn nằm trong mipmap ?, hãy thử @ mipmap / ic_launcher.
Khemraj

@VivekMishra Bạn có thể dán nhật ký lỗi liên quan của mình không? Bạn đã thêm phương thức này vào lớp sử dụng ràng buộc của mình chưa?
Khemraj

**** / lỗi ràng buộc dữ liệu **** msg: Không thể tìm thấy getter cho thuộc tính 'app: src' với loại giá trị java.lang.String trên com.zuowei.circleimageview.CircleImageView. Tôi đã thử với cả Android cũng như không gian tên ứng dụng và cả hai đều không hiệu quả với tôi. Tôi cũng đã thay thế ImageView mặc định với circleImageView trong tham số
Vivek Mishra

Ngoài ra tôi đã tạo ra ràng buộc Adaptor trong một lớp học riêng biệt
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

bạn có thể làm như sau

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

2

Tôi không phải là chuyên gia về Android nhưng tôi đã dành hàng giờ để cố gắng giải mã các giải pháp hiện có. Điều tốt là tôi nắm bắt được toàn bộ ý tưởng về việc sử dụng liên kết dữ liệu BindingAdaptertốt hơn một chút. Vì điều đó, tôi ít nhất cũng biết ơn vì những câu trả lời hiện có (mặc dù chưa đầy đủ). Dưới đây là bản phân tích đầy đủ về cách tiếp cận:

Tôi cũng sẽ sử dụng BindingAdaptertrong ví dụ này. Chuẩn bị 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>

Vì vậy, ở đây tôi chỉ giữ lại những thứ quan trọng:

  • SomeViewModellà của tôi, ViewModeltôi sử dụng để ràng buộc dữ liệu. Bạn cũng có thể sử dụng một lớp mở rộng BaseObservablevà sử dụng @Bindable. Tuy nhiên, BindingAdaptertrong ví dụ này, không nhất thiết phải ở trong một ViewModelhoặc BaseObservablelớp học! Một lớp học đơn giản sẽ làm! Điều này sẽ được minh họa sau.
  • app:appIconDrawable="@{model.packageName}". Vâng ... điều này thực sự khiến tôi đau đầu! Hãy chia nhỏ nó:
    • app:appIconDrawable: Đây có thể là bất cứ thứ gì app:iCanBeAnything:! Có thật không. Bạn cũng có thể giữ "android:src"! Tuy nhiên, hãy lưu ý về sự lựa chọn của bạn, chúng ta sẽ sử dụng nó ở phần sau!
    • "@ {model.packageName}": Nếu bạn đã làm việc với liên kết dữ liệu , thì điều này quen thuộc. Tôi sẽ chỉ cách sử dụng nó sau.

Giả sử chúng ta sử dụng lớp quan sát đơn giản này:

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

Như đã hứa, bạn cũng có thể di chuyển public static void setImageViewDrawable(), sang một số lớp khác, ví dụ: có thể bạn có thể có một lớp có tập hợp BindingAdapters:

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

Một nhận xét quan trọng khác là trong Observablelớp học của tôi, tôi đã từng String packageNamechuyển thông tin bổ sung cho setImageViewDrawable. Bạn cũng có thể chọn ví dụ int resourceId, với getters / setters tương ứng, mà bộ điều hợp trở thành:

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

Điều này làm việc cho tôi. tôi sẽ thêm nó vào câu trả lời @hqzxzwb dưới dạng nhận xét nhưng do giới hạn danh tiếng.

Tôi có cái này trong mô hình xem của tôi

var passport = R.drawable.passport

Sau đó, trong xml của tôi, tôi có

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

Và đó là nó


OK nhưng bạn quên đề cập rằng bạn phải nhập ngữ cảnh. Bạn có thể cập nhật câu trả lời của mình không?
deadfish

1

Sử dụng Fresco (thư viện hình ảnh 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

Trong trạng thái xem của bạn hoặc xem lớp mô hình;

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

Trong XML của bạn;

<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

đặt hình ảnh như thế này,

  <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" />
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.