这应该是大部分Android开发者在使用ListView时会碰到问题,在解决这个问题前,需要先了解什么叫触摸模式。
触摸模式(Touch Mode),在Android SDK的官方文档中有详细解释(http://developer.android.com/resources/articles/touch-mode.html),其大概内容如下:
用户通过触摸屏操作设备时,设备将自动进入触摸模式,相对的,使用键盘、轨迹球等其他设备时,设备处于非触摸模式
进入触摸模式后,View将失去焦点(Focus)和选择(Selection)状态(文本输入框是例外),原因是Android的设计者没有好的办法解决同时操作触摸屏和轨迹球、键盘等外部设备所带来的混乱状态
Android的设计者认为进入触摸模式后,View失去焦点和选择状态不是一个需要修正的问题,开发者也不应该在触摸模式中试图保持View的焦点或选择状态,可以使用CheckBox或RadioButton等控件来标识选择状态
这种设计策略可以说完全是从工程师的思维而不是从用户的需求去考虑问题,新的手机的性能越来越强,屏幕分辨率越来越高,应用程序界面设计趋向复杂,使用触摸屏操作时居然连个高亮显示都无法做到,真是件十足坑爹的事。
考察了几种不同的实现后,评估其性能代价和解决方式的优雅程度,我决定用checked状态来模拟选择选择selection来实现ListView的选中行的高亮显示:
将ListView设为CHOICE_MODE_SINGLE或CHOICE_MODE_MULTIPLE,使ListView支持checked/unchecked状态
在ListView的适配器中,返回的View实现Checkable接口,跟随选中状态的改变而改变自身背景
下面是我的测试代码:
package org.noodies;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Checkable;
import android.widget.ListView;
import android.widget.TextView;
public class TestListActivity extends Activity {
private ListView mListView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView) findViewById(R.id.listView1);
// 设为单选,允许列表项切换checked/unchecked状态
mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// 列表的选择效果设为透明,由列表项自行维护各状态显示
mListView.setSelector(android.R.color.transparent);
mListView.setAdapter(new TestAdapter());
}
private class MyView extends TextView implements Checkable {
private final int[] STATE_CHECKED = { android.R.attr.state_checked };
private int[] mSavedState;
private boolean mChecked = false;
public MyView(Context context) {
super(context);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
updateBackground();
}
}
public boolean isChecked() {
return mChecked;
}
public void toggle() {
setChecked(!mChecked);
}
private void updateBackground() {
Drawable bg = this.getBackground();
// 在这里切换checked/unchecked状态
if (bg.getClass().equals(StateListDrawable.class)) {
if (isChecked()) {
mSavedState = bg.getState();
bg.setState(STATE_CHECKED);
} else if (mSavedState != null) {
bg.setState(mSavedState);
}
}
}
}
private class TestAdapter extends BaseAdapter {
private String[] testData = { "test1", "test2", "abcd", "abcdefg" };
public int getCount() {
return testData.length;
}
public Object getItem(int position) {
return testData[position];
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
TextView v;
if (convertView == null) {
v = new MyView(TestListActivity.this);
v.setBackgroundResource(R.drawable.list_item_bg);
} else {
v = (TextView) convertView;
}
v.setText(testData[position]);
return v;
}
}
}
需要建立一个selector作为View的背景,包含选中状态的图像:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/list_item_bg_checked" android:state_checked="true"/>
<item android:drawable="@drawable/list_item_bg_normal" android:state_pressed="false"/>
<item android:drawable="@drawable/list_item_bg_pressed" android:state_pressed="true"/>
</selector>