How to Dynamically Change Android ListView Text Colors

2 Comments

The default Android ListView object is difficult to work with if creativity is one of your design factors. In my apps, I like to offer users the preference of choosing a light or dark color scheme as they desire. This requires programatically changing every object displayed on the screen on the fly once the user exits the preference screen. It’s no problem changing the background color of a ListView object, but changing the TextColor is a whole other issue. There is no native hook to reach the TextView field used to display the ListView’s text. There used to be in earlier versions of Android, but I’m writing for Froyo and higher. The android:setTextColor directive is no longer available in the ListView object. To further complicate matters, my ListView objects are displayed on a TabWidget. I have one ListView for each tab. For a 5-tabbed app, I have to change 5 ListViews.

Let’s start this tutorial with a simple Linear Layout for the TabHost as our main.xml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122
23
24
2526
27
28
2930
31
32
3334
35
36
3738
39
40
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:hapticFeedbackEnabled="true">
<LinearLayout android:id="@+id/main_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<ListView android:id="@+id/tab_list_1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" /><ListView android:id="@+id/tab_list_2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" /><ListView android:id="@+id/tab_list_3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" /><ListView android:id="@+id/tab_list_4"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" /><ListView android:id="@+id/tab_list_5"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" /></FrameLayout>
</LinearLayout>
</TabHost>
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:hapticFeedbackEnabled="true">
<LinearLayout android:id="@+id/main_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<ListView android:id="@+id/tab_list_1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
<ListView android:id="@+id/tab_list_2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
<ListView android:id="@+id/tab_list_3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
<ListView android:id="@+id/tab_list_4"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
<ListView android:id="@+id/tab_list_5"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
</FrameLayout>
</LinearLayout>
</TabHost>

Notice the highlighted code at lines 21, 25, 29, 33 and 37. Setting the android:cacheColorHint to #00000000 overrides a common issue developers face when customizing the ListView background. By default, many Android widgets, ListView included, have a transparent background. This means you can see through the default window’s background. ListView also enables fading edges by default. The fading edges technique is used throughout the system to indicate that a container can be scrolled. Fading edges is a gradient display rendered as a very dark gray to black fade. The fade effect is implemented using a combination of Canvas.saveLayerAlpha() and the Porter-Duff Destination Out blending mode. When we customize the background color, the blending optimzation leaves part of the dark gradient at the bottom of the screen. Scrolling the ListView changes all the TextView abjects back to their original color. To disable the optimization, simply use the transparent color #00000000 and you won’t have any problems.

Next we’re going to create a layout for the ListView rows. This file is called tab_list_layout.xml. Start this file by coping the simple_list_item_1 layout from the Android SDK.

Simple_list_item_1.xml from the Android SDK:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
 
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>

Tab_list_layout.xml:

1
2
3
4
5
6
78
9
10
1112
13
14
15
16
1718
19
20
2122
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_text_light"android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/app_text_color_light"android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_text_dark"android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/app_text_color_dark"android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_text_light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/app_text_color_light"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_text_dark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/app_text_color_dark"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
</LinearLayout>

In our layout file, we’ve created two TextView objects. One for the dark color scheme and one for the light. Name them accordingly (lines 7 and 17) and assign the desired colors to them (lines 11 and 21). The colors are defined in your colors.xml resource.

Next we’re going to setup our adapter. Remember, the text color is driven by our application user preference. So first we have to retrieve the user preference:

1
2
3
45
6
7
89
10
// retrieve user color scheme preference
My_App app = (My_App) this.getApplication();
IsLightColors = app.RetrieveBoolean(getString(R.string.LightColorsKey));
int list_text_id = (IsLightColors) ? R.id.list_text_light : R.id.list_text_dark; // defined in tab_list_layout.xml 
// setup ListView adapter
ListView list=(ListView)findViewById(R.id.tab_list_1); // defined in main.xml
String[] listitems = getResources().getStringArray(R.array.ListItems); //defined in strings.xmlArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.tab_list_layout, list_text_id, listitems);
list.setAdapter(adapter);
// retrieve user color scheme preference
My_App app = (My_App) this.getApplication();
IsLightColors = app.RetrieveBoolean(getString(R.string.LightColorsKey));
int list_text_id = (IsLightColors) ? R.id.list_text_light : R.id.list_text_dark; // defined in tab_list_layout.xml

// setup ListView adapter
ListView list=(ListView)findViewById(R.id.tab_list_1); // defined in main.xml
String[] listitems = getResources().getStringArray(R.array.ListItems); //defined in strings.xml
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.tab_list_layout, list_text_id, listitems);
list.setAdapter(adapter);

Line 4 is the key to changing the colors dynamically. The variable list_text_id is set to the id number of appropriate TextView widget defined in tab_list_layout.xml depending on the user’s color scheme preference. The list_text_id variable is passed to the ArrayAdapter as a parameter. Take note: if you are using a tabbed interface, you need to create a separate adapter for each tab on which you place a ListView widget. R.array.ListItems is the actual text you want to appear in the ListView rows defined in strings.xml.

1
2
3
4
5
6
7
8
<string-array name="ListItems">
<item>Item 1</item>
<item>Item 2</item>
<item>Item 3</item>
<item>Item 4</item>
<item>Item 5</item>
<item>Item 6</item>
</string-array>
<string-array name="ListItems">
<item>Item 1</item>
<item>Item 2</item>
<item>Item 3</item>
<item>Item 4</item>
<item>Item 5</item>
<item>Item 6</item>
</string-array>

Okay, so the setup is all complete, everything up till now will get you to the point where the colors will change when you first start your app. How do we code to change the text colors dynamically immediately after a user changes their preference?

When we instantiate the preferences activity, we start it to return a result. When the result is returned from the preference activity, we run the code to instantiate our ListView adapter(s). In my case, I’ve created a method called SetTabColors() which I call twice. The first time after I create the tabs in the onCreate mehtod, the second time whenever a user changes their preferences.

1
2
3
4
5
6
7
8
9
10
11
12
1314
15
16
17
18
19
20
21
2223
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// create the options menu when the user presses the menu key
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.mainmenu, menu);
return true;
}
 
@Override
public boolean onOptionsItemSelected(MenuItem item) {
 
if (item.getItemId() == R.id.settings_menu_item) {
Intent intent = new Intent().setClass(this,PreferenceActivity.class);
this.startActivityForResult(intent, 0);
} else if (item.getItemId() == R.id.exit_app_item) {
finish();
}
 
return true;
}
 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setTabColors();
}
 
protected void setTabColors() {
 
/**
* Before we do anything, determine saved value of IsLightColors
*/
Mobile_SDLC_App app = (Mobile_SDLC_App) this.getApplication();
IsLightColors = app.RetrieveBoolean(getString(R.string.LightColorsKey));
 
// set the color scheme and resource based on IsLightColors user prefs
int tab_indicator = IsLightColors ? R.drawable.tab_indicator_v4_light : R.drawable.tab_indicator_v4;
int layoutColor = IsLightColors ? Color.WHITE : Color.BLACK;
int list_text_id = (IsLightColors) ? R.id.list_text_light : R.id.list_text_dark;
 
View myLayout = findViewById(R.id.main_layout);
myLayout.setBackgroundColor(layoutColor);
 
// set the background resource for each tab
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) {
tabHost.getTabWidget().getChildAt(i).setBackgroundResource(tab_indicator);
}
 
//ListView
ListView list=(ListView)findViewById(R.id.tab_list);
String[] listitems = getResources().getStringArray(R.array.ListItems);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.tab_list_layout, list_text_id, listitems);
list.setAdapter(adapter);
 
}
// create the options menu when the user presses the menu key
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.mainmenu, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

if (item.getItemId() == R.id.settings_menu_item) {
Intent intent = new Intent().setClass(this,
PreferenceActivity.class);
this.startActivityForResult(intent, 0);
} else if (item.getItemId() == R.id.exit_app_item) {
finish();
}

return true;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setTabColors();
}

protected void setTabColors() {

/**
* Before we do anything, determine saved value of IsLightColors
*/
Mobile_SDLC_App app = (Mobile_SDLC_App) this.getApplication();
IsLightColors = app.RetrieveBoolean(getString(R.string.LightColorsKey));

// set the color scheme and resource based on IsLightColors user prefs
int tab_indicator = IsLightColors ? R.drawable.tab_indicator_v4_light : R.drawable.tab_indicator_v4;
int layoutColor = IsLightColors ? Color.WHITE : Color.BLACK;
int list_text_id = (IsLightColors) ? R.id.list_text_light : R.id.list_text_dark;

View myLayout = findViewById(R.id.main_layout);
myLayout.setBackgroundColor(layoutColor);

// set the background resource for each tab
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) {
tabHost.getTabWidget().getChildAt(i).setBackgroundResource(tab_indicator);
}

//ListView
ListView list=(ListView)findViewById(R.id.tab_list);
String[] listitems = getResources().getStringArray(R.array.ListItems);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.tab_list_layout, list_text_id, listitems);
list.setAdapter(adapter);

}

2 comments on “How to Dynamically Change Android ListView Text Colors

  1. Can you talk about notifyDataChanged method please.

  2. Phan on said:

    My list view record in Database and I want set color for 1 RECORD ( ONLY 1 ). How ??? Please tell me by e-mail. Thanks !!!
    I use Cursor and add cursor to SimpleCursorAdapter. Then, I add SimpleCursorAdapter to listView by code :
    listContent.setAdapter(cursorAdapter);

    Please tell me, thanks !!!
    ( Sorry, My English is BAD but I can read the your answer. Thank you !!! ).

Speak Your Mind

*

HTML tags are not allowed.

s2Member®