Set text color using data binding on Android-ThrowExceptions

Exception or error:

I’m trying to set TextView text color using data binding library

android:textColor="@{holder.getTitleColor(context, item)}"

where the method in Holder class is defined like below

public int getTitleColor(Context context, Item item) {
   ...
}

No matter if I return color int (@ColorInt) or color resource (@ColorRes) it paints the text solid white. What am I doing wrong?

How to solve:

I seems the int you are providing is interpreted as a hex color, even though it seem intuitive that this setter should be expecting a resource ID.

use the Context reference generated for each bindable view, and use it to convert the resource ID to the color you are pointing to, as described in the DataBinding Dev Guide:

A special variable named context is generated for use in binding expressions as needed. The value for context is the Context from the root View’s getContext().

use it to set color like this:

 <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.text}"
            android:textColor="@{context.getColor(data.colorRes)}"
            />

Edit

for backwards compatibility you can use ContextCompat. Import needed:

<layout>
    <data>
        <import type="android.support.v4.content.ContextCompat"/>
        ...
    </data>
    ...
     <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.text}"
            android:textColor="@{ContextCompat.getColor(context, data.colorRes)}"
            />
</layout>

###

create method by using BindingAdapter

@BindingAdapter({"bind:color"})
public static void setFont(TextView textView, Item item) {
    textView.setTextColor(<set color of your choice>);
}

and to call it from xml

app:color="@{item}"

###

For setting part of the string to a color – This works perfectly with Kotlin, string resources, and Databinding


  • Add your binding adapter (place this outside all your classes)

    @BindingAdapter("app:full_text", "app:span_text", "app:span_color")
    fun formatText(textView: TextView, full_text: String, span_text: String, span_color: Int) {
        val firstMatchingIndex = full_text.indexOf(span_text)
        val lastMatchingIndex = firstMatchingIndex + span_text.length
        val spannable = SpannableString(full_text)
        spannable.setSpan(ForegroundColorSpan(span_color), firstMatchingIndex, lastMatchingIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
        textView.text = spannable
    }
    
  • Setup string resources with variables

    <string name="percentage">%1$d\%%</string>
    <string name="booking_fee">Require card and collect %1$s at Booking</string>
    
  • Convert value to string in your ViewHolder (if you need to)

    fun bind(percentage: Int) {
        binding.percentage = context.resources.getString(R.string.percentage, percentage)
        binding.executePendingBindings()
    }
    
  • Apply bindings via your xml layout

    <data>
        <variable
            name="percentage"
            type="String" />
    </data>
    
    <TextView
        ...
        app:full_text="@{@string/booking_fee(percentage)}"
        app:span_color="@{@color/color_primary}"
        app:span_text="@{percentage}" />
    

Result:

enter image description here


Do not use android:text="..." in your layout file

###

In addition to the solution of @Mardann, here is an updated solution that also works on API lower than 23 by using ContextCompat.getColor():

<layout>

    <data>
        <import type="androidx.core.content.ContextCompat" />
        <variable
            name="data"
            type="com.example.myapp.MyDataObject" />
    </data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{data.text}"
        android:textColor="@{ContextCompat.getColor(context, data.colorRes)}"/>
    </layout>
  • Make sure to import ContextCompat as shown above.
  • You can automagically ‘context’ as method parameter for ContextCompat.getColor() because it will be automatically resolved to the view’s context.

###

In my case, the color value was in a String Format(eg. “#000000”)

1.String TxtColor = “#000000”

2.Import “android.graphics.Color”

<layout>
    <data>
      <import type="android.graphics.Color"/>
      <variable name="txtColor" type="String"/>
    </data>
     .... other views

</layout>

3.Set to Desired View — In my case it was TextView

    ........ other views
  <android.support.v7.widget.AppCompatTextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textcolor= "@{Color.parseColor(txtColor)}" //when we import android.graphics.Color we can access it's all methods present
        tools:text="Test"/>
       ...... other views

4.Binding from Activity/Adapter –in my case it was Adapter

inner class ViewHolder(private val binding: BindingClass) :
    RecyclerView.ViewHolder(binding.root) {

    fun setData(data: DataClass, TxtColor : String?) {
        binding.txtColor= TxtColor 
        binding.executePendingBindings()
    }
}

###

Create Binding Adapter as follows, here I am passing all the strings to be colored inside {}. Replace the {blah} string with colored blah string in span.

@BindingAdapter( "spanColor")
fun formatText(view:TextView, hexColorValue:Int) {
    val text = view.text
    val span = SpannableStringBuilder(text)
    var i = 0
    var diff = 0
    while (i < text.length) {
        val firstIndex = text.indexOf('{', i) - diff
        val secondIndex = text.indexOf('}', i) - diff
        if (firstIndex < 0 || secondIndex < 0) break
        span.delete(firstIndex, firstIndex + 1)
        span.delete(secondIndex - 1, secondIndex)
        span.setSpan(ForegroundColorSpan(hexColorValue), firstIndex, secondIndex-1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        i = secondIndex + diff + 1
        diff += 2
    }
    view.text = span
}

In your XMl file use the attribute (app:spanColor="@{@color/colorAccent}") as

 <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:layout_marginTop="@dimen/space_xlarge"
                style="@style/DefaultSmallText"
                app:spanColor="@{@color/colorAccent}"
                android:text="@string/create_credential_message"/>

string.xml

<string name="create_credential_message"><![CDATA[{Username} must at least contain 8 alphanumeric characters or an email address. {Password} must be 8-20 characters long, contain uppercase, lowercase, number, & special characters.]]></string>

###

To set color from an integer just call:

android:textColor="@{data.color}"

###

You can also use binding adapter and then use color resource.

Define this method anywhere in project:

@BindingAdapter(value = "text_color") //customise your name here 
public static void setTextColor(TextView view, int color) {
    view.setTextColor(color);
}

And then in XML use your new attribute:

<TextView
    app:text_color="@{@color/colorPrimary}"/>

###

My solution was to use this TextView declaration in the xml:

<TextView
                ...
                android:textColor="@{model.getResultColor(context, index)}"
                ...
                />

and this method in the viewModel:

fun getResultColor(index: Int): Int {
    getItem(index)?.let { item ->
...

    return Color.GRAY
}

###

object CustumBinderAdapter {
    @JvmStatic
    @BindingAdapter("text_color")
    fun setTextColor(view: TextView, color: Int) {
        when(color){
            R.color.soft_green->{
                view.setTextColor(Color.parseColor("#5abc6e"))
            }
            R.color.deep_orange->{
                view.setTextColor(Color.parseColor("#e62e30"))
            }
        }
    }

}

and in XML use like this:

app:text_color="@{yourViewModel.yourIntColorValue}"

Leave a Reply

Your email address will not be published. Required fields are marked *