|
In one of our
earlier posts in this Fancy ListViews series, Michal asked “could you make also
a short tutorial on changing the background image and text color of the selection
in ListView?”
Of course, we at AndroidGuys love fan mail! So, let’s talk about
changing the way selections look in ListViews. As with many things
in Android development, there’s the simple answer, the realization that the simple
answer isn’t so simple, and some workarounds.
In theory, changing the selection bar is a matter of setting
the android:listSelector property on the ListView widget
in the layout XML, or using the equivalent setSelector() methods on
the ListView object itself. In practice, well, let’s say there are
issues…
Let’s first take a simple case: we want to change the color of
the selection bar to something else, such as green. In the layout XML, the change
is a single line:
- <?xml
version="1.0"
encoding="utf-8"?>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
>
- <TextView
-
android:id="@+id/selection"
-
android:layout_width="fill_parent"
-
android:layout_height="wrap_content"/>
- <ListView
-
android:id="@android:id/list"
-
android:layout_width="fill_parent"
-
android:layout_height="fill_parent"
-
android:drawSelectorOnTop="false"
-
android:listSelector="@drawable/green"
-
/>
- </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
android:listSelector="@drawable/green"
/>
</LinearLayout>
All we did was add android:listSelector="@drawable/green".
Of course, we need to define that Drawable. In this case, since we’re
settling for a solid color, a PaintDrawable defined in res/values/colors.xml
will suffice:
- <resources>
- <drawable
name="green">#f0f0</drawable>
- </resources>
<resources>
<drawable name="green">#f0f0</drawable>
</resources>
That change alone will give us a ListView where
the selection bar is green.
That’s nice and all, but suppose we want to do something more
elaborate, something more exciting, something that speaks to the way the world is
today.
In other words, rather than have list items be solely highlighted
with a bar, we want to have them be pointed to by the Flying Fickle Finger of Fate.
(NOTE: any resemblance of this Flying Fickle Finger of Fate to
the
original is purely coincidental and terribly amusing)
For the Flying Fickle Finger of Fate (or FFFF for short), we’ll
use a suitable icon, courtesy of
OpenClipArt.org:

The goal is to have the currently-selected list entry be pointed
to by the finger. Sounds easy, right?
After all, since android:listSelector takes a reference
to a Drawable resource, all we need to do is drop a suitably-sized
PNG of FFFF into res/drawable, point to it from android:listSelector,
set aside some whitespace on the left of our row layout to accommodate the icon,
and it “just works”. Right?
Right?
Well, not exactly.
Android will attempt to stretch whatever PNG drawable you use
as the list selector, so it takes up the full width of the ListView.
In fact, I suspect it really would like you to use the so-called “Nine Patch” style
PNG, where you use an outer border of pixels to provide instructions to Android
for how to stretch the PNG.
I tried that and got…unpleasant results. Whether that was the
result of my error in coding, my error in understanding how to make PNG-based list
selectors work, or a bug in the M5 SDK, is still under investigation.
But the Flying Fickle Finger of Fate will not be denied. So,
let’s take a look at how you “manually” adjust the way selected items looks. This
also covers Michal’s second request — to see how to adjust other properties of the
selected row besides the background image.
Working from the code first demonstrated in a
previous post, let’s make a few changes.
First, we add an ImageView to our rows:
- <?xml
version="1.0"
encoding="utf-8"?>
- <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- >
- <ImageView
-
android:id="@+id/ffff"
-
android:layout_width="81px"
-
android:layout_height="41px"
- />
- <ImageView
-
android:id="@+id/icon"
-
android:layout_width="22px"
-
android:paddingLeft="2px"
-
android:paddingRight="2px"
-
android:paddingTop="2px"
-
android:layout_height="wrap_content"
-
android:src="@drawable/ok"
- />
- <TextView
-
android:id="@+id/label"
-
android:layout_width="fill_parent"
-
android:layout_height="fill_parent"
-
android:layout_gravity="center_vertical"
-
android:textSize="25sp"
- />
- </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/ffff"
android:layout_width="81px"
android:layout_height="41px"
/>
<ImageView
android:id="@+id/icon"
android:layout_width="22px"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="2px"
android:layout_height="wrap_content"
android:src="@drawable/ok"
/>
<TextView
android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_vertical"
android:textSize="25sp"
/>
</LinearLayout>
Note how we neglected to provide the android:src
attribute in the layout XML. Without a source, the ImageView simply
paints nothing, showing the background. But, since we specified a size in pixels,
it will take up that amount of space. In this case, we chose a pixel size that matches
with an FFFF icon we prepared.
Next, we update our ViewWrapper to give us access
to the FFFF:
- package com.commonsware.android.fancylists.seven;
-
- import android.view.View;
- import android.widget.ImageView;
- import android.widget.TextView;
-
- class ViewWrapper {
- View base;
- TextView label=null;
- ImageView icon=null;
- ImageView ffff=null;
-
- ViewWrapper(View base) {
-
this.base=base;
- }
-
- TextView getLabel() {
-
if (label==null)
{
-
label=(TextView)base.findViewById(R.id.label);
- }
-
-
return(label);
- }
-
- ImageView getIcon() {
-
if (icon==null)
{
-
icon=(ImageView)base.findViewById(R.id.icon);
- }
-
-
return(icon);
- }
-
- ImageView getFlyingFickleFingerOfFate()
{
-
if (ffff==null)
{
-
ffff=(ImageView)base.findViewById(R.id.ffff);
- }
-
-
return(ffff);
- }
- }
package com.commonsware.android.fancylists.seven;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
class ViewWrapper {
View base;
TextView label=null;
ImageView icon=null;
ImageView ffff=null;
ViewWrapper(View base) {
this.base=base;
}
TextView getLabel() {
if (label==null) {
label=(TextView)base.findViewById(R.id.label);
}
return(label);
}
ImageView getIcon() {
if (icon==null) {
icon=(ImageView)base.findViewById(R.id.icon);
}
return(icon);
}
ImageView getFlyingFickleFingerOfFate() {
if (ffff==null) {
ffff=(ImageView)base.findViewById(R.id.ffff);
}
return(ffff);
}
}
Next, we add an OnItemSelectedListener to the
ListView. As one might expect, this gets invoked every time an item
is selected in the list, or when nothing is selected. There are two separate callback
methods: onItemSelected() and onNothingSelected(). Our
mission in these callbacks is to draw the FFFF on the selected row and to remove
any previous FFFF from the previous selection.
In onItemSelected(), we see if we already have fingered
a row (lastFinger!=null); if so, we null out the image Drawable,
causing that ImageView to return to its original background-only state.
We then get the current row’s ViewWrapper, get our FFFF placeholder,
and have it actually draw the FFFF:
- public
void onItemSelected(AdapterView parent, View
v, int position, long
id) {
- if (lastFinger!=null)
{
- lastFinger.setImageDrawable(null);
- }
-
- ViewWrapper wrapper=(ViewWrapper)v.getTag();
-
- lastFinger=wrapper.getFlyingFickleFingerOfFate();
- lastFinger.setImageResource(R.drawable.ffff);
- }
public void onItemSelected(AdapterView parent, View v, int position, long id) {
if (lastFinger!=null) {
lastFinger.setImageDrawable(null);
}
ViewWrapper wrapper=(ViewWrapper)v.getTag();
lastFinger=wrapper.getFlyingFickleFingerOfFate();
lastFinger.setImageResource(R.drawable.ffff);
}
In onNothingSelected(), we simply null out the previous
finger (if there was one), both in terms of the image displayed and in terms of
the lastFinger variable.
- public
void onNothingSelected(AdapterView parent)
{
- if (lastFinger!=null)
{
- lastFinger.setImageDrawable(null);
- lastFinger=null;
- }
- }
public void onNothingSelected(AdapterView parent) {
if (lastFinger!=null) {
lastFinger.setImageDrawable(null);
lastFinger=null;
}
}
The result is almost what we were after, complete with the green
bar discussed earlier:

There are two flaws in this implementation as seen in the M5
SDK emulator. First, we get a FFFF icon on the first row when the activity starts,
even though the row is not selected. That is because onItemSelected()
is inexplicably called without that row being selected. Second, if you click on
a row, you will get the green bar without the FFFF. In fact, the FFFF is cleared
from the previous selection. Apparently, the current version of Android treats clicks
and selections as being different from a callback standpoint, but the same from
a display standpoint (i.e., uses android:listSelector). We will revisit
all of these issues sometime after the next SDK release and see how the ListView
behaves at that time.
While this demo shows activating or deactivating an icon in the
row, you could do whatever you want to the View that makes up the row. So, if Michal
wanted to change the text color of the selected row, that’s merely a matter of getting
one’s hands on the desired TextView (perhaps via a wrapper or holder),
and then call setTextColor().
Of course, setTextColor() is remarkably more complicated
than one might expect, worthy of a future Building ‘Droids post in its own right.
Next time, in our final episode of the Fancy ListViews series,
we will go back to the checklist scenario from before and wrap up the checkable-row
logic into a CheckListView widget that one could use as a drop-in replacement
for ListView. Until then, always remember: ask not to whom the Flying
Fickle Finger of Fate points, it points to thee.
Source: http://androidguys.com/2008/07/28/fancy-listviews-part-five/
 |
Mlb Jerseys sports jerseys
cheap jerseys