51

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?

tomrozb
  • 25,773
  • 31
  • 101
  • 122

12 Answers12

84

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"/>
        <variable name="data" type="com.whatever.myapp.MyModel"/>
        ...
    </data>
    ...
     <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.text}"
            android:textColor="@{ContextCompat.getColor(context, data.colorRes)}"
            />
</layout>
Mardann
  • 1,953
  • 1
  • 16
  • 23
  • 17
    If you try to use this solution in a device with API level lower than 23, you get an error indicating "java.lang.NoSuchMethodError". For those who need a solution for previous versions, use this: **android:textColor="@{context.getResources().getColor(data.colorRes)}"** – Fer May 29 '17 at 09:43
  • 1
    where does this "data" object come from? are you not supposed to set it to R.color.my_colour for example? – Jono Jun 10 '20 at 14:12
  • @Jonathan - added clarification to sample code. "data" represents the bound model object – Mardann Jun 11 '20 at 07:42
  • 6
    And in Androidx you'll need – alisonthemonster Jul 16 '20 at 16:15
  • The use of `context` as shown in the answer also implies theme-awareness. E.g. if you use `ViewModels` you might be tempted to get the color from [getApplication()](https://developer.android.com/reference/android/arch/lifecycle/AndroidViewModel#getApplication()) but that will always result in default theme colors. – l33t Jun 22 '21 at 23:52
  • is there any chance to memory leak for using ContextCompat in import? Any idea? – Shihab Uddin Jul 07 '21 at 11:09
12

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.
Mehlyfication
  • 498
  • 5
  • 9
10

create method by using BindingAdapter

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

and to call it from xml

app:color="@{item}"
l33t
  • 18,692
  • 16
  • 103
  • 180
Ravi
  • 34,851
  • 21
  • 122
  • 183
  • Any explanation why my method doesn't work? It returns the int that's needed by android:textColor, I've no idea what's wrong with the code in the question. – tomrozb Oct 07 '16 at 18:59
  • put some log in your method and check whether its going there or not, because with databinding i dont think so you can call method like this and that also with return value – Ravi Oct 08 '16 at 09:20
8

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()
    }
}
Rajesh Naddy
  • 1,120
  • 12
  • 17
2

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

Michael
  • 9,639
  • 3
  • 64
  • 69
2

Create an extension function for textview.

@BindingAdapter("android:colorId")
fun TextView.setTextColorValue(@ColorRes colorId: Int) {
    if (colorId == 0) return
    setTextColor(ContextCompat.getColor(context, colorId))
}

use it in xml like this

<TextView
            android:id="@+id/deliveryStatus"
            android:colorId="@{model.deliveryStatusColor}" />
SajithK
  • 832
  • 10
  • 17
1

To set color from an integer just call:

android:textColor="@{data.color}"
Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
1

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}"/>
Jan Moravec
  • 496
  • 6
  • 9
0

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>
Tasneem
  • 793
  • 8
  • 24
0

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
}
marcolav
  • 405
  • 1
  • 6
  • 17
0
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}"
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
  • note:-> android:textColor="@{ContextCompat.getColor(context, data.colorRes)}" gives exception following exception in some case : Fatal Exception: android.content.res.Resources$NotFoundException Resource ID #0x0 So i have to do workarround .And above solution is working – sankyrahul Apr 16 '20 at 05:18
  • This is because binding gives default value 0 for integers when initialising. you can return in this case to avoid that exception. Look at my answer. – SajithK May 22 '21 at 07:21
0

Code:

binding.color = ContextCompat.getColor(requireContext(), R.color.text_regular)

Layout:

    <variable
        name="color"
        type="Integer" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@{color}"/>
Konstantin Konopko
  • 5,229
  • 4
  • 36
  • 62