一 原理分析
ListView是Android中非常重要的控件之一,Android对ListView做了特殊的设计与优化,尤其是在显示大量数据时,使用ListView的小技巧,可以得到更好的用户体验,下面就来介绍这些常用的技巧。
1)复用convertView
ListView在需要显示Item的时候,会首先检查回收站里是否有缓存的item,如果发现有缓存的item,ListView会直接复用它,把它作为参数传递给Adapter的getView方法,参数名为convertView。
所以如果convertView不为空,表明回收站中存在可以复用的Item,就不需要在创建新的Item了。
这种View复用的方式适用于单一Item视图和多种Item视图的情况,在之前的例子中已经提到过,可以参考以前的讲解。
View复用的示例代码如下:
1 | public View getView(int position, View convertView, ViewGroup parent) { |
2 | if (convertView == null) { |
3 | convertView = getLayoutInflater().inflate(R.layout.text_item, parent,false); |
2)使用ViewHolder
在Android中,在操作一个控件的时,首先需要通过findViewById从控件树中找到它,然后才能对它进行操作。而对于ListView中的Item布局文件的解析式重复性的,因此每次都执行findViewById方法非常耗时。
常用的做法是定义一个ViewHolder类来缓存查找后的控件引用,这样只需要在初始化的时候查找一次,以后对控件的操作都可以直接从ViewHolder中获取。
02 | public View getView(int position, View convertView, ViewGroup parent) { |
07 | if(convertView == null) { |
09 | vh = new ViewHolder(); |
11 | if(getItemViewType(position) == FIRST_LETTER_ITEM) { |
12 | convertView = getLayoutInflater().inflate(R.layout.first_letter_item, parent, false); |
14 | vh.tv = (TextView) convertView.findViewById(R.id.firstletter); |
17 | convertView = getLayoutInflater().inflate(R.layout.word_item, parent, false); |
19 | vh.tv = (TextView) convertView.findViewById(R.id.word); |
21 | convertView.setTag(vh); |
23 | vh = (ViewHolder) convertView.getTag(); |
25 | vh.tv.setText(letter[position]); |
其中,通过setTag和getTag的方式来存储和获取ViewHolder。省去了每次执行findViewById的时间。
3)刷新ListView的数据
Adapter处于ListView和数据的中间,当有数据变化时需要Adapter通知ListView刷新显示的内容。Adapter 提供了notifyDataSetChanged()和notifyDataSetInvalidated()两个方法通知ListView刷新。当有数据更新时调用notifyDataSetChanged方法,当数据完全无效时调用notifyDataSetInvalidated方法。
4)Header和Footer
ListView除了显示Item以为,还可以显示Header和Footer。ListView提供了addHeaderView和addFooterView方法添加Header和Footer。需要注意的是:必须在给ListView设置Adapter之前,调用这两个方法添加header或者footer,否则会抛出异常。
5)使用selector美化listView
通过设置ListView的listSelector属性,可以为listView的Item设置选中,点击等显示效果。在Android中可以使用listView的setSelector方法或者在xml文件中设置android:listSelector属性来设置ListView的selector属性。还可以设置ListView的android:drawSelectorOnTop属性,把selector绘制在item背景之后。
6)在ListView的Item之间显示分割线
通过ListView的android:divider属性或者setDivider方法可以修改Item之间的分割线。也可以给android:divider属性设置图片、颜色,或者设置为@drawable/@null(表示无分割线)。在使用android:divider属性时,同时还可以使用dividerHeight属性设置分割线占据的高度。
7)使用transcriptMode和stackFromBottom属性
ListView有两个比较特殊的属性android:transcriptMode和android: stackFromBottom。使用transcriptMode属性可以在有数据变化的时候让listView自动滚动到底部。transcriptMode可设置为一下三个不同的值:
disabled:禁用transcriptMode属性;
normal:如果最后一个item可见,滚动到底部;
alwaysScroll:总是自动滚动到底部;
使用stackFromBottom属性可以设置item从底部向上开始排列。通常在聊天、短信类型的应用中使用stackFromBottom和transcriptMode属性可以得到很好的显示效果。
效果如下图所示:

图1 短信应用中ListView的显示效果
8) ListView的其他属性
以下几个属性和方法在ListView的使用中也比较重要,列举如下:
android:fastScrollEnabled;
android:smoothScrollbar;
android:cacheColorHint;
android:fadingEdge="vertical|horizontal|none";
setTextFilterEnabled()方法;
在此不做过多介绍,读者可以自行查资料,并进行验证。
二 示例分析
下面通过两个demo,将以上所讲到的ListView的特性分别进行演示和验证。
1) Demo1
JAVA1代码如下:
01 | package com.devdiv.test.listviewtest7; |
03 | import android.app.Activity; |
04 | import android.os.Bundle; |
05 | import android.view.View; |
06 | import android.widget.ArrayAdapter; |
07 | import android.widget.Button; |
08 | import android.widget.ListView; |
10 | public class ListViewTest7Activity extends Activity { |
13 | ArrayAdapter<String> mAdapter; |
15 | public static final String CHEESES[] = { |
16 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", |
17 | "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca","Ambert", "American Cheese", |
18 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", |
19 | "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca","Ambert", "American Cheese", |
20 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", |
21 | "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca","Ambert", "American Cheese", |
22 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", |
23 | "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca","Ambert", "American Cheese", |
26 | /** Called when the activity is first created. */ |
28 | public void onCreate(Bundle savedInstanceState) { |
29 | super.onCreate(savedInstanceState); |
30 | setContentView(R.layout.main); |
33 | mAdapter = new ArrayAdapter<String>(this, R.layout.my_list_item, CHEESES); |
35 | mListView = (ListView) findViewById(R.id.list); |
39 | Button mButton = new Button(this); |
40 | mButton.setText("this is the header"); |
41 | mListView.addHeaderView(mButton); |
43 | Button mButton2 = new Button(this); |
44 | mButton2.setText("this is the footer"); |
45 | mListView.addFooterView(mButton2); |
47 | mListView.setAdapter(mAdapter); |
49 | mListView.setTextFilterEnabled(true); |
53 | public void onDrawSelectorOnTop(View v) { |
54 | mListView.setSelector(R.drawable.selector_on_top); |
55 | mListView.setDrawSelectorOnTop(true); |
59 | public void onUseSelectorAsBackground(View V) { |
60 | mListView.setSelector(R.drawable.selector_as_background); |
61 | mListView.setDrawSelectorOnTop(false); |
所引用的布局文件main.xml的内容如下:
01 | <?xml version="1.0" encoding="utf-8"?> |
03 | android:layout_width="fill_parent" |
04 | android:layout_height="fill_parent" |
05 | android:orientation="vertical" > |
08 | android:id="@+id/list" |
09 | android:layout_width="fill_parent" |
10 | android:layout_height="0dp" |
11 | android:layout_weight="1" |
13 | android:fastScrollEnabled="true" |
15 | android:fadingEdge="horizontal" |
16 | android:fadingEdgeLength="10dp" |
18 | android:divider="#efefef" |
19 | android:dividerHeight="2dp" |
24 | style="@android:style/ButtonBar" |
25 | android:layout_width="fill_parent" |
26 | android:layout_height="wrap_content" |
27 | android:orientation="horizontal"> |
30 | android:layout_width="0dp" |
31 | android:layout_height="fill_parent" |
32 | android:layout_weight="1" |
33 | android:layout_gravity="center_vertical" |
34 | android:text="@string/draw_selector_on_top" |
35 | android:onClick="onDrawSelectorOnTop" |
39 | android:layout_width="0dp" |
40 | android:layout_height="fill_parent" |
41 | android:layout_weight="1" |
42 | android:layout_gravity="center_vertical" |
43 | android:text="@string/use_selector_as_background" |
44 | android:onClick="onUseSelectorAsBackground" /> |
下面,对JAVA代码和布局文件进行分析。
JAVA代码中,为ListView设置header和footer的代码部分如下:
1 | Button mButton = new Button(this); |
2 | mButton.setText("this is the header"); |
3 | mListView.addHeaderView(mButton); |
5 | Button mButton2 = new Button(this); |
6 | mButton2.setText("this is the footer"); |
7 | mListView.addFooterView(mButton2); |
此段代码的功能为ListView添加两个按钮作为header和footer,并在按钮上显示提示性的文字。
之后对布局文件中的两个按钮添加onClick事件:
01 | public void onDrawSelectorOnTop(View v) { |
02 | mListView.setSelector(R.drawable.selector_on_top); |
03 | mListView.setDrawSelectorOnTop(true); |
07 | public void onUseSelectorAsBackground(View V) { |
08 | mListView.setSelector(R.drawable.selector_as_background); |
09 | mListView.setDrawSelectorOnTop(false); |
在布局文件中,分别将两个按钮的onClick属性设置为对应的函数名:
1 | android:onClick="onDrawSelectorOnTop" |
2 | android:onClick="onUseSelectorAsBackground" /> |
在public void onDrawSelectorOnTop(View v) 中,将ListView的Selector设置为drawable文件夹下的selector_on_top.xml文件,selector_on_top.xml文件的内容如下:
01 | <?xml version="1.0" encoding="utf-8"?> |
04 | android:state_pressed="true" |
05 | android:drawable="@drawable/list_selector_on_top_pressed" /> |
08 | android:state_focused="true" |
09 | android:drawable="@drawable/list_selector_on_top_focused" /> |
12 | android:drawable="@android:color/transparent" /> |
在public void onUseSelectorAsBackground(View V) 中,将ListView的Selector设置为drawable文件夹下的selector_as_background.xml文件,selector_as_background.xml文件的内容如下:
01 | <?xml version="1.0" encoding="utf-8"?> |
04 | android:state_pressed="true" |
05 | android:drawable="@drawable/list_selector_as_background_pressed" /> |
08 | android:state_focused="true" |
09 | android:drawable="@drawable/list_selector_as_background_focused" /> |
12 | android:drawable="@android:color/transparent" /> |
为ListView添加divider的代码如下:
1 | android:divider="#efefef" |
2 | android:dividerHeight="2dp" |
2) Demo2
Demo2的作用主要是验证ListView的transcriptMode和stackFromBottom属性。实现的功能为用户从编辑框输入文字,点击回车按钮后,文字自动添加到ListView的底部,跟短信应用的效果非常相似。主要参考android提供API Demo中的list12。
JAVA代码如下:
01 | package com.devdiv.test.listviewtest8; |
03 | import java.util.ArrayList; |
05 | import android.app.ListActivity; |
06 | import android.os.Bundle; |
07 | import android.view.KeyEvent; |
08 | import android.view.View; |
09 | import android.view.View.OnClickListener; |
10 | import android.view.View.OnKeyListener; |
11 | import android.widget.ArrayAdapter; |
12 | import android.widget.EditText; |
13 | import android.widget.ListView; |
15 | public class ListViewTest8Activity extends ListActivity { |
17 | private EditText mEditText; |
19 | private ArrayAdapter<String> mAdapter; |
21 | private ArrayList<String> mStrings = new ArrayList<String>(); |
23 | private ListView mListView; |
25 | /** Called when the activity is first created. */ |
27 | public void onCreate(Bundle savedInstanceState) { |
28 | super.onCreate(savedInstanceState); |
29 | setContentView(R.layout.main); |
31 | mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mStrings); |
33 | setListAdapter(mAdapter); |
35 | mEditText = (EditText) findViewById(R.id.userText); |
37 | mEditText.setOnClickListener(new OnClickListener() { |
40 | public void onClick(View v) { |
46 | mEditText.setOnKeyListener(new OnKeyListener() { |
49 | public boolean onKey(View v, int keyCode, KeyEvent event) { |
51 | if (event.getAction() == KeyEvent.ACTION_DOWN) { |
53 | case KeyEvent.KEYCODE_DPAD_CENTER: |
54 | case KeyEvent.KEYCODE_ENTER: |
64 | public void submitText() { |
65 | String mString = mEditText.getText().toString(); |
66 | mStrings.add(mString); |
67 | mEditText.setText(null); |
68 | mAdapter.notifyDataSetChanged(); |
布局文件main.xml的内容如下:
01 | <?xml version="1.0" encoding="utf-8"?> |
04 | android:orientation="vertical" |
05 | android:layout_width="fill_parent" |
06 | android:layout_height="fill_parent" |
07 | android:paddingLeft="8dip" |
08 | android:paddingRight="8dip"> |
10 | <ListView android:id="@android:id/list" |
11 | android:layout_width="fill_parent" |
12 | android:layout_height="0dip" |
13 | android:layout_weight="1" |
14 | android:stackFromBottom="true" |
15 | android:transcriptMode="normal"/> |
17 | <EditText android:id="@+id/userText" |
18 | android:layout_width="fill_parent" |
19 | android:layout_height="wrap_content" /> |
其中,transcriptMode和stackFromBottom属性设置为:
1 | android:stackFromBottom="true" |
2 | android:transcriptMode="normal"/> |
在submitText()方法中,提交和刷新数据,采用notifyDataSetChanged()的方式。代码如下:
1 | public void submitText() { |
2 | String mString = mEditText.getText().toString(); |
4 | mEditText.setText(null); |
5 | mAdapter.notifyDataSetChanged(); |
三 运行效果
1) Demo1

图2 ListView中的header

图3 ListView中的footer

图4 点击“Draw selector on top”按钮后Item选中时的效果

图5 点击“Draw selector as background”按钮后item选中的效果
2) Demo2

http://www.devdiv.com/Android-listview_-thread-124257-1-1.html