使用DataBinding库设置背景颜色资源或null。

64

我想使用DataBinding库在我的视图上设置背景色或null,但尝试运行它时出现异常。

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference

这是我的做法:

android:background="@{article.sponsored ? @color/sponsored_article_background : null}"

我还尝试了设置转换,但它没有起作用。

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

最终,我使用 @BindingAdapter 的变通解决了它,但我想知道如何正确地做。


1
正如您在堆栈跟踪中显示的那样,它需要原始类型:int,因此您不能传递null。 - Sedat Polat
你能试一下:convertColorToDrawable(android.R.color.transparent); - Sedat Polat
事实是,该视图没有默认的颜色,并且绘制透明颜色效率低下。我只想清除该视图的背景。 - Damian Petla
7个回答

108

原因:

首先要知道的是,DataBinding库已经提供了位于android.databinding.adapters.Converters.convertColorToDrawable(int)convertColorToDrawable绑定转换器。

使用android:background 理论上应该可以工作,因为它有对应的setBackground(Drawable) 方法。问题在于它会看到你试图将一个颜色作为第一个参数传递,所以会在应用到setBackground(Drawable)方法之前尝试启动此转换器。如果数据绑定决定使用转换器,则它将在两个参数上同时使用它,因此也会在应用最终结果到setter之前,在null上使用它。
因为null无法转换为int(也无法调用intValue()),所以会抛出NullPointerException

官方的Data Binding指南中提到,不支持混合参数类型。

以下是这个问题的两个解决方案。虽然您可以使用这两种任意一种解决方案,但第一种更容易实现。

解决方案:

1. 作为drawable

如果您将颜色定义为资源中的drawable而不是颜色(它可以在我们的colors.xml文件中):

<drawable name="sponsored_article_background">#your_color</drawable>
或者
<drawable name="sponsored_article_background">@color/sponsored_article_background</drawable>

那么你应该能够像最初希望的那样使用android:background,但需要提供可绘制对象而不是颜色:

android:background="@{article.sponsored ? @drawable/sponsored_article_background : null}"

这里的参数类型兼容:第一个是 Drawable,第二个是 null,所以它也可以转换为 Drawable

2. 作为资源 ID

app:backgroundResource="@{article.sponsored ? R.color.sponsored_article_background : 0}"

但是这还需要在data部分添加你的R类导入:

<data>
    <import type="com.example.package.R" />
    <variable ... />
</data>

将0作为“null资源ID”传递是安全的,因为ViewsetBackgroundResource方法会检查resid是否与0不同,如果相同,则将null设置为背景可绘制对象。这样就不会创建不必要的透明可绘制对象。

public void setBackgroundResource(int resid) {
    if (resid != 0 && resid == mBackgroundResource) {
        return;
    }

    Drawable d= null;
    if (resid != 0) {
        d = mResources.getDrawable(resid);
    }
    setBackgroundDrawable(d);

    mBackgroundResource = resid;
}

我正在尝试使用您的第二种方法,但我不确定如何使用“app:backgroundResource”。我在app命名空间中找不到此属性。您能提供一些详细信息吗? - marrock
在使用DataBinding时,这样的属性可能并不一定存在,但它应该能够正常工作。请查看https://developer.android.com/tools/data-binding/guide.html#attribute_setters以及其中的“自动设置器”部分。 - Maciej Ciemięga
1
为什么没有人提到使用@android:color/transparent。对我来说,这听起来就像没有背景一样。 - LeoFarage
2
我在我的模型上有这个方法: public int getBackground(){ return read ? R.color.read_sms : R.color.unread_sms;} 并使用了 app:backgroundResource = "@{sms.getBackground}",效果完美。 - Laranjeiro
1
@LeoFarage 虽然看起来可能相同,但从技术上讲并不相同;使用颜色会导致创建一个ColorDrawable。尽管该drawable最终不会执行任何操作,就像任何其他对象一样,但创建它需要时间并使用资源。由于它实际上并没有做任何事情,所以这是有些浪费的。这是否会对性能产生重大影响取决于具体情况。 - Lorne Laliberte
显示剩余3条评论

25

我认为你应该尝试使用默认的颜色,而不是null

像这样

android:background="@{article.sponsored ? @color/sponsored_article_background : @color/your_default_color}"

我没有该视图的默认颜色。如果文章不是赞助的,背景应该被清除。 - Damian Petla
1
您的默认颜色可以是透明的(@android:color/transparent)。 - DragonT
这是最正确的答案...其他的对我没用,而且 R.color.xyz 给了我一个奇怪的默认蓝色。 - niranjan kurambhatti

11

你可以采用一种方法,编写自定义的@BindingConversion来帮你处理这个问题:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return color != 0 ? new ColorDrawable(color) : null;
}

使用这个方法,你可以将接受ColorDrawable的任何属性设置为整数颜色值(如0或@android:color/transparent),并自动将其转换为轻量级@null。

(而内置的convertColorToDrawable(int)转换器总是创建一个ColorDrawable对象,即使颜色是透明的。)

注意:为了使用该方法替换内置的@BindingConversion,它必须返回一个ColorDrawable而不是Drawable,否则内置方法将被视为更具体/合适。


另一种方法是在数据绑定表达式中使用静态方法将颜色转换为Drawable,以使值类型匹配。例如,你可以导入内置的Converters类:

<data>
    <import type="androidx.databinding.adapters.Converters"/>
</data>

...并像这样编写您的表达式:

android:background="@{article.sponsored ? Converters.convertColorToDrawable(@color/sponsored_article_background) : null}"

虽然我个人建议将这种条件逻辑放在数据绑定适配器方法中,例如使用一个getArticleBackground()方法返回Drawable或null。一般来说,如果避免在布局文件中放置决策逻辑,事情会更容易调试和跟踪。


1
<import type="androidx.databinding.adapters.Converters"/> 的翻译是 androidX 导入。 - denizs

1

试试这个:

@Bindable
private int color;

在构造函数中。
color = Color.parseColor("your color in String for examp.(#ffffff)")

在 XML 中:
android:textColor = "@{data.color}"

在哪里使用@Bindable? - ghita

0
在这篇文章中,您可以找到两个好的解决方案,但在我的情况下,只有一个适用,因为我想改变材料按钮的背景色调,以下是我的绑定适配器:
首先,创建一个Kotlin文件并粘贴此适配器方法:
package com.nyp.kartak.utilities

import android.content.res.ColorStateList
import androidx.databinding.BindingAdapter
import com.google.android.material.button.MaterialButton
import com.nyp.kartak.model.ReceiptUserPurchaseModel

@BindingAdapter("backgroundTintBinding")
fun backgroundTintBinding(button: MaterialButton, model: ReceiptUserPurchaseModel) {
    button.backgroundTintList = ColorStateList.valueOf(button.resources.getColor( model.color))
}

其次,在您的XML中使用它:

<data>
    <variable
        name="model"
        type="com.nyp.kartak.model.ReceiptUserPurchaseModel" />
</data>

// .....

    <com.google.android.material.button.MaterialButton
        android:id="@+id/payBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{model.getAction()}"
        app:backgroundTintBinding="@{model}" />

0

如果您只想设置backgroundTint而不是整个background,则另一种解决方案是使用以下方式:

如果您的最小API为21,则需要导入ContextCompat

<import type="androidx.core.content.ContextCompat" />

...

app:backgroundTintList="@{ContextCompat.getColorStateList(context, [funtion_to_get_your_color_res_id]))}"

0

这是一篇很老的帖子,但我想再提供一个解决方案。

  1. 在DRAWABLE中声明自定义样式/背景

我有四个类似的样式,所以我将只粘贴其中一个。

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/colorAccent">
    <item>
        <layer-list>
            <item>
                <shape android:shape="oval">
                    <stroke
                        android:width="1dp"
                        android:color="@color/colorAccent" />
                    <solid android:color="@color/colorPrimaryDark" />
                    <size
                        android:width="200dp"
                        android:height="200dp" />
                </shape>
            </item>
            <item
                android:width="100dp"
                android:height="100dp"
                android:gravity="center"
                android:drawable="@drawable/ic_car_white" />
        </layer-list>
    </item>
</ripple>

当我设置这个样式时,我的按钮看起来像下面这样:

enter image description here

  1. 在您的模型/处理程序类中准备可绑定值

在我的情况下,我在ActivityMainEventHandler类中有以下代码

@Bindable
public Drawable getConenctButtonStyle() {
    // here i'm checking connection state but you can do own conditions
    ConnectionState state = Communication.getInstance().getConnectionState();
    if (state != null) switch (state) {
        case CONNECTED:
            return ctx.getDrawable(R.drawable.circle_btn_state_green);
        case CONNECTING:
        case DISCONNECTING:
            return ctx.getDrawable(R.drawable.circle_btn_state_orange);
        case DISCONNECTED:
            return ctx.getDrawable(R.drawable.circle_btn_state_red);
    }
    return ctx.getDrawable(R.drawable.circle_btn_state_first);
}

将您的类传递给视图

在Activity onCreate中:

 bind = DataBindingUtil.setContentView(this, R.layout.activity_main);
        handler = new ActivityMainEventHandler(this, bind);
        bind.setMainHandler(handler);

我们活动的XML

   <data>
            <variable
                name="mainHandler"
                type="xx.xxx.packagename.eventHandlers.ActivityMainEventHandler" />
        </data>
  1. 将您的背景设置为以下视图

标记:

 android:background="@{mainHandler.conenctButtonStyle}"

然后,如果您想再次检查第2步的条件并重新绘制视图,请调用以下代码:
//BR.conenctButtonStyle it's automatically generated id
bind.notifyPropertyChanged(BR.conenctButtonStyle);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接