Ustaw identyfikator zasobu do rysowania w android: src dla ImageView przy użyciu powiązania danych w systemie Android

91

Próbuję ustawić identyfikator zasobu do rysowania na android: src z ImageView przy użyciu powiązania danych

Oto mój obiekt:

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

    // ...
}

Oto mój układ:

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

I wreszcie klasa aktywności:

// ...

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

    // ...

}

W ogóle nie wyświetla obrazu. Co ja robię źle?

Swoją drogą, działał doskonale w standardowy sposób:

@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
źródło

Odpowiedzi:

112

Odpowiedź z 10 listopada 2016 r

Poniższy komentarz Splash podkreślił, że nie jest konieczne używanie niestandardowego typu właściwości (np. imageResource), Zamiast tego możemy utworzyć wiele metod dla android:srctego typu:

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

Stara odpowiedź

Zawsze możesz spróbować użyć adaptera:

public class DataBindingAdapters {

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

Następnie możesz użyć adaptera w swoim xml w ten sposób

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

Pamiętaj, aby zauważyć, że nazwa w xml pasuje do adnotacji BindingAdapter (imageResource)

Klasa DataBindingAdapters nie musi być nigdzie zadeklarowana w szczególności, mechanika DataBinding znajdzie to bez znaczenia (uważam)

Joe Maher
źródło
Działa za pomocą @BindingAdapter. Ale wartość powinna być android:srcnie imageResource: @BindingAdapter("android:src"). Dziękuję Ci!
Yuriy Seredyuk
5
@YuriySeredyuk Nooooo! haha, proszę. Zrobienie tego spowoduje nadpisanie każdego pojedynczego użycia „android: src” w kodzie XML w całej aplikacji, czego zdecydowanie NIE chcesz robić. Aby imageResource działał, musisz zmienić xml dla imageView, tak jak zrobiłem powyżej, powiązanie danych pokaże, co tam umieścić
Joe Maher
1
OK, zrozumiałem pomysł. Teraz za pomocą <ImageView imageResource="@{recipe.imageResource}" />i @BindingAdapter("imageResource"). Właśnie przegapiłem imageResource="@{recipe.imageResource}"fragment twojego kodu :)
Yuriy Seredyuk
1
Czy nie musi tak być app:imageResource?
NameSpace
1
„Wykonanie tego spowoduje nadpisanie każdego pojedynczego użycia„ android: src ”w kodzie XML w całej aplikacji” Czy wiązanie danych nie jest wystarczająco inteligentne, aby zastosować ten atrybut tylko do ImageView, ponieważ to jest zdefiniowane w funkcji? Myślę, że „android: src” byłby lepszy .... zastanów się, co sam Android robi dla adapterów wiążących ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash
41

W ogóle nie jest potrzebny zwyczaj BindingAdapter. Po prostu użyj

app:imageResource="@{yourResId}"

i będzie działać dobrze.

Sprawdzić to na jak to działa.

hqzxzwb
źródło
2
powinno to mieć więcej głosów pozytywnych, ponieważ jest to świetna odpowiedź około 2020 roku
JohnnyLambada
zdecydowanie najlepsza i najprostsza odpowiedź
luckyhandler
To wygląda na najlepszą i najbardziej odpowiednią odpowiedź na koniec 2020 r.
mcy
Patrzę na ImageViewklasę i podążam za setImageResourcemetodą, wydaje się, że w końcu jest rozwiązany resolveUrii jest, jeśli nie zero, walidacja. Więc to zadziałałoby Int, zastanawiam się, co by się stało, gdyby Int?. Gdy powiązania są wykonywane, na przykład, jeśli wywoła coś innego, executePendingBindingswówczas nie dopuszcza wartości null, domyślnie przyjmuje wartość zero, wartości null.
cutiko
25

definiować:

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

posługiwać się:

<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
źródło
gdzie dodać tę metodę
myatmins
wsparcie dodaj go w dowolnej klasie, być może możesz utworzyć ImageDataBindingAdapter.class.
qinmiao
12

Nigdy nie zastępuj standardowych atrybutów SDK podczas tworzenia własnych @BindingAdapter!

To nie jest dobre podejście z wielu powodów, takich jak: zapobiegnie uzyskaniu korzyści z nowych poprawek aktualizacji Android SDK dla tego atrybutu. Może to również dezorientować programistów i na pewno trudne do ponownego wykorzystania (ponieważ nie oczekuje się nadpisania)

możesz użyć innej przestrzeni nazw, na przykład:

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

lub

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

------ Rozpocznij aktualizację 2 lipca 2018 r

Nie zaleca się używania przestrzeni nazw, więc lepiej polegać na prefiksie lub innej nazwie, na przykład:

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

lub

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

------ koniec Aktualizacja 2. lipca 2018

Poleciłbym jednak inne rozwiązanie, ponieważ:

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

widok kontekstu jest zawsze dostępny wewnątrz wyrażenia powiązania @{ ... }

Maher Abuthraa
źródło
1
Uważam, że kodu wewnątrz XML należy unikać w miarę możliwości - nie da się go przetestować, może się nagromadzić i nie jest to oczywiste. Ale zgadzam się, że przeciążanie standardowych atrybutów może być mylące. Myślę, że najlepszym sposobem jest nazwanie nowego atrybutu inaczej, w tym przypadku "srcResId", ale nadal użyj BindingAdapter
Kirill Starostin
7

Opierając się na odpowiedzi Mahera Abuthraa, oto, czego ostatecznie użyłem w XML:

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

contextZmienna jest dostępna w ekspresji wiązania bez importu. Nie jest też BindingAdapterpotrzebny żaden zwyczaj . Jedyne zastrzeżenie: metoda getDrawablejest dostępna dopiero od API 21.

Tom Lądek
źródło
6

Na Kotlin umieścić to na najwyższym poziomie pliku utils, brak kontekstu static / towarzysz potrzebne:

@BindingAdapter("android:src")
fun setImageViewResource(view: ImageView, resId : Int) {
    view.setImageResource(resId)
}
joecks
źródło
5

Im więcej możesz z tym zrobić DataBindingAdapter

Ustaw dowolny z tych typów:

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

Ustaw obraz błędu / obraz zastępczy

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

Przetestowano wszystkie typy

SC

Staje się to możliwe dzięki pojedynczemu adapterowi do wiązania. Po prostu skopiuj ten projekt metody.

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

Powód, dla którego użyłem Glide do załadowania wszystkich obiektów

Jeśli zapytasz mnie, dlaczego użyłem Glide do załadowania identyfikatora do rysowania / zasobu, zamiast tego mogę użyć imageView.setImageBitmap();lub imageView.setImageResource();. Więc powód jest taki

  • Glide to wydajna platforma do ładowania obrazów, która obejmuje dekodowanie nośników, pamięć i buforowanie dysku. Więc nie musisz się martwić o duże obrazy i pamięć podręczną.
  • Aby zachować spójność podczas ładowania obrazu. Teraz Glide ładuje wszystkie rodzaje zasobów graficznych.

Jeśli używasz Piccaso, Fresso lub jakiejkolwiek innej biblioteki do ładowania obrazów, możesz wprowadzić zmiany w loadImageWithGlidemetodzie .

Khemraj
źródło
`errorImage =" @ {@ drawable / ic_launcher} "`. Ta rzecz nawet się nie kompiluje
Vivek Mishra,
@VivekMishra Być może twój ic_launcher jest w mipmap ?, spróbuj @ mipmap / ic_launcher.
Khemraj
@VivekMishra Czy możesz wkleić swój odpowiedni dziennik błędów? Czy dodałeś tę metodę w swojej klasie użytkowej powiązań?
Khemraj,
**** / błąd powiązania danych **** msg: Nie można znaleźć metody pobierającej dla atrybutu „app: src” o typie wartości java.lang.String na com.zuowei.circleimageview.CircleImageView. Próbowałem zarówno z przestrzenią nazw Androida, jak i aplikacji i oba nie działały dla mnie. Zastąpiłem też domyślny imageview na circleImageView w parametrze
Vivek Mishra
Również stworzyłem oprawę Adapter w osobnej klasie
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}"/>
android_developer
źródło
3

możesz wykonać następujące czynności

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"
Fahad Alotaibi
źródło
2

Nie jestem ekspertem od Androida, ale godzinami próbowałem rozszyfrować istniejące rozwiązania. Dobrą rzeczą jest to, że BindingAdapternieco lepiej uchwyciłem cały pomysł wiązania danych . Za to jestem przynajmniej wdzięczny za istniejące odpowiedzi (choć bardzo niekompletne). Oto kompletny podział podejścia:

Będę również użyć BindingAdapterw tym przykładzie. Przygotowanie 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>

Więc tutaj zachowuję tylko ważne rzeczy:

  • SomeViewModeljest moim ViewModelużywanym do wiązania danych. Możesz także użyć klasy, która rozszerza BaseObservablei używać @Bindable. Jednak BindingAdapterw tym przykładzie nie musi być w klasie ViewModelani BaseObservable! Zwykła klasa wystarczy! Zostanie to zilustrowane później.
  • app:appIconDrawable="@{model.packageName}". Tak ... to naprawdę przyprawiało mnie o bóle głowy! Rozbijmy to:
    • app:appIconDrawable: To może być wszystko app:iCanBeAnything:! Naprawdę. Możesz też zatrzymać "android:src"! Zwróć jednak uwagę na swój wybór, użyjemy go później!
    • „@ {model.packageName}”: Jeśli pracowałeś z wiązaniem danych , to jest znajome. Pokażę, jak to jest używane później.

Załóżmy, że używamy tej prostej klasy 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));
   }
}

Zgodnie z obietnicą możesz również przenieść public static void setImageViewDrawable(), do innej klasy, np. Możesz mieć klasę, która ma kolekcję BindingAdapters:

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

Kolejną ważną uwagą jest to, że w mojej Observableklasie String packageNameprzekazywałem dodatkowe informacje do setImageViewDrawable. Możesz też wybrać np. int resourceIdZ odpowiednimi pobierającymi / ustawiającymi parametrami, dla których adapter staje się:

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
źródło
2

Ta praca dla mnie. dodałbym to do odpowiedzi @hqzxzwb jako komentarz, ale z powodu ograniczeń reputacji.

Mam to w widoku Model

var passport = R.drawable.passport

Następnie w moim xml mam

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

I to wszystko

Raines
źródło
OK, ale zapomniałeś wspomnieć, że musisz zaimportować kontekst. Czy mógłbyś zaktualizować swoją odpowiedź?
Deadfish
1

Korzystanie z Fresco (biblioteka obrazów na Facebooku)

 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));
        }
    }
}
최봉재
źródło
0

W Twoim stanie widoku lub widoku klasy modelu;

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

W swoim XML;

<androidx.appcompat.widget.AppCompatImageButton
   .
   .
   .
   android:src="@{viewState.getSource(context)}"
Cafer Mert Ceyhan
źródło
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
źródło
0

ustaw obraz w ten sposób,

  <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
źródło