Defina o ID do recurso drawable em android: src para ImageView usando vinculação de dados no Android

94

Estou tentando definir o ID de recurso drawable para android: src de ImageView usando vinculação de dados

Aqui está meu objetivo:

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

    // ...
}

Aqui está meu layout:

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

E, finalmente, aula de atividade:

// ...

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

    // ...

}

Ele não exibe nenhuma imagem. O que estou fazendo errado?

Aliás, estava funcionando perfeitamente com a maneira padrão:

@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
fonte

Respostas:

114

Resposta em 10 de novembro de 2016

O comentário do Splash abaixo destacou que não é necessário usar um tipo de propriedade personalizada (como imageResource), podemos criar vários métodos para android:src:

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

Resposta Antiga

Você sempre pode tentar usar um adaptador:

public class DataBindingAdapters {

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

Você pode então usar o adaptador em seu xml assim

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

Certifique-se de observar que o nome dentro do xml corresponde à anotação BindingAdapter (imageResource)

A classe DataBindingAdapters não precisa ser declarada em nenhum lugar em particular, a mecânica do DataBinding vai descobrir que não importa (eu acredito)

Joe Maher
fonte
Funciona usando @BindingAdapter. Mas, o valor deve ser android:src, não imageResource: @BindingAdapter("android:src"). Obrigado!
Yuriy Seredyuk
5
@YuriySeredyuk Nããão! haha por favor. Isso substituirá cada uso de "android: src" dentro do xml em todo o seu aplicativo, o que você definitivamente NÃO quer fazer. Para fazer com que imageResource funcione, você precisa alterar o xml para imageView como fiz acima, a ligação de dados resolverá o que colocar lá
Joe Maher
1
OK, eu entendi a ideia. Agora usando <ImageView imageResource="@{recipe.imageResource}" />e @BindingAdapter("imageResource"). Eu só perdeu imageResource="@{recipe.imageResource}"parte do seu código cortou :)
Yuriy Seredyuk
1
Não precisa ser assim app:imageResource?
NameSpace
1
"Fazer isso substituirá cada uso de" android: src "dentro do xml em todo o seu aplicativo" A vinculação de dados não é inteligente o suficiente para aplicar apenas esse atributo a ImageView, porque é o que está definido na função? Acho que "android: src" seria preferível .... considere o que o próprio Android faz para adaptadores de vinculação ImageView: android.googlesource.com/platform/frameworks/data-binding/+/…
Splash
45

Não há necessidade de costume BindingAdapter. Apenas use

app:imageResource="@{yourResId}"

e vai funcionar bem.

Verifique isso para saber como ele funciona.

hqzxzwb
fonte
2
isso deve ter mais
votos positivos
definitivamente, a melhor e mais simples resposta
luckyhandler
Esta parece ser a melhor e mais adequada resposta no final de 2020
março às 12h36
Estou dando uma olhada na ImageViewaula e seguindo o setImageResourcemétodo, parece que eventualmente está resolvido resolveUrie não há senão zero validação. Então isso funcionaria para Inteu me pergunto o que poderia acontecer se Int?. Quando as ligações são executadas, por exemplo, se alguma outra coisa chamar executePendingBindingsentão não anulável são padronizados para zero, anuláveis ​​para nulo.
cutiko de
25

definir:

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

usar:

<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
fonte
onde adiciono esse método
myatmins
suporte adicioná-lo em qualquer classe, talvez você possa criar um ImageDataBindingAdapter.class.
qinmiao
12

Nunca substitua os atributos padrão do SDK ao criar o seu próprio @BindingAdapter!

Essa não é uma boa abordagem por muitos motivos como: ela vai impedir a obtenção de benefícios de novas correções na atualização do Android SDK nesse atributo. Além disso, pode confundir os desenvolvedores e, certamente, ser complicado para reutilização (porque não pode ser substituído)

você pode usar um namespace diferente, como:

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

ou

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

------ iniciar a atualização 2.Jul.2018

O uso de namespace não é recomendado, portanto, é melhor confiar em um prefixo ou nome diferente como:

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

ou

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

------ finalizar a atualização 2.Jul.2018

No entanto, eu recomendaria uma solução diferente como:

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

a visão de contexto está sempre disponível dentro da expressão de ligação @{ ... }

Maher Abuthraa
fonte
1
Acho que o código dentro do xml deve ser evitado o máximo possível - não é testável, pode se acumular e não é óbvio. Mas concordo que sobrecarregar os atributos padrão pode ser confuso. Acho que a melhor maneira é nomear o novo atributo de forma diferente, neste caso "srcResId", mas ainda usar um BindingAdapter
Kirill Starostin
8

Com base na resposta de Maher Abuthraa, isso é o que acabei usando no XML:

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

A contextvariável está disponível na expressão de ligação sem quaisquer importações. Além disso, nenhum costume BindingAdapternecessário. Apenas ressalva: o método getDrawableestá disponível apenas a partir da API 21.

Tom Ladek
fonte
6

Para Kotlin, coloque isso em um arquivo utils de nível superior, nenhum contexto estático / complementar necessário:

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

Quanto mais você pode fazer com DataBindingAdapter

Defina qualquer um destes tipos:

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

Definir imagem de erro / imagem de espaço reservado

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

Testado todos os tipos

SC

Isso se torna possível com um único adaptador de ligação. Basta copiar este projeto de método.

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

Motivo pelo qual usei o Glide para carregar todos os objetos

Se você me perguntar por que usei o Glide para carregar a id do drawable / recurso, em vez disso, poderia usar imageView.setImageBitmap();ou imageView.setImageResource();. Então, a razão é que

  • Glide é uma estrutura de carregamento de imagens eficiente que envolve decodificação de mídia, memória e cache de disco. Portanto, você não precisa se preocupar com imagens grandes e cache.
  • Para tornar a consistência ao carregar a imagem. Agora todos os tipos de recursos de imagem são carregados pelo Glide.

Se você usa o Piccaso, Fresso ou qualquer outra biblioteca de carregamento de imagens, pode fazer alterações no loadImageWithGlidemétodo .

Khemraj
fonte
`errorImage =" @ {@ drawable / ic_launcher} "`. Essa coisa nem mesmo compila para mim
Vivek Mishra
@VivekMishra Talvez seu ic_launcher esteja no mipmap ?, tente @ mipmap / ic_launcher.
Khemraj
@VivekMishra Você pode colar seu registro de erros relevante? Você adicionou este método em sua classe de utilitário de ligação?
Khemraj
**** / erro de ligação de dados **** msg: Não é possível encontrar o getter para o atributo 'app: src' com tipo de valor java.lang.String em com.zuowei.circleimageview.CircleImageView. Eu tentei com o Android e também com o namespace do aplicativo e os dois não funcionaram para mim. Também substituí a visualização de imagem padrão por circleImageView no parâmetro
Vivek Mishra
Além disso, criei um adaptador de ligação em uma classe separada
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}"/>
msevgi
fonte
3

você pode fazer o seguinte

android:src="@{expand?@drawable/ic_collapse:@drawable/ic_expand}"
Fahad Alotaibi
fonte
2

Não sou especialista em Android, mas passei horas tentando decifrar as soluções existentes. A coisa boa é que entendi toda a ideia de vinculação de dados usando BindingAdapterum pouco melhor. Por isso, estou pelo menos grato pelas respostas existentes (embora bastante incompletas). Aqui está uma análise completa da abordagem:

Também vou usar o BindingAdapterneste exemplo. Preparando o 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>

Portanto, aqui estou mantendo apenas as coisas importantes:

  • SomeViewModelé o que ViewModeleu uso para vinculação de dados. Você também pode usar uma classe que estende BaseObservablee usa @Bindable. No entanto, BindingAdapterneste exemplo, não precisa estar em uma classe ViewModelou BaseObservable! Uma aula simples é suficiente! Isso será ilustrado mais tarde.
  • app:appIconDrawable="@{model.packageName}". Sim ... isso estava realmente me causando dores de cabeça! Vamos decompô-lo:
    • app:appIconDrawable: Isso pode ser qualquer coisa app:iCanBeAnything:! Mesmo. Você também pode manter "android:src"! No entanto, tome nota da sua escolha, iremos utilizá-la mais tarde!
    • "@ {model.packageName}": se você trabalhou com vinculação de dados , isso é familiar. Vou mostrar como isso é usado mais tarde.

Vamos supor que usamos esta classe Observable simples:

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

Conforme prometido, você também pode mover o public static void setImageViewDrawable(), para alguma outra classe, por exemplo, talvez você possa ter uma classe que tenha uma coleção de BindingAdapters:

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

Outra observação importante é que na minha Observableaula eu costumava String packageNamepassar informações extras para o setImageViewDrawable. Você também pode escolher, por exemplo int resourceId, com os getters / setters correspondentes, para os quais o adaptador se torna:

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
fonte
2

Este trabalho para mim. Eu teria adicionado à resposta @hqzxzwb como comentário, mas devido a limitações de reputação.

Eu tenho isso na minha visão Modelo

var passport = R.drawable.passport

Então no meu xml, eu tenho

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

E é isso

Raines
fonte
OK, mas você se esqueceu de mencionar que deve importar o contexto. Você poderia atualizar sua resposta?
deadfish
1

Usando Fresco (biblioteca de imagens do 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));
        }
    }
}
최봉재
fonte
0

Em seu estado de exibição ou classe de modelo de exibição;

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

Em seu XML;

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

definir imagem como esta,

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