一 原理分析
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属性可以得到很好的显示效果。
效果如下图所示:
![QQ截图20120514191351.png QQ截图20120514191351.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwMzZ8NzI3MzJiZmV8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
图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
![QQ截图20120514151439.png QQ截图20120514151439.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwNDF8ZDY2OTQyODJ8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
图2 ListView中的header
![QQ截图20120514151453.png QQ截图20120514151453.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwNDB8N2RkZjA4OWJ8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
图3 ListView中的footer
![QQ截图20120514151511.png QQ截图20120514151511.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwMzl8MGFkOTgyZGF8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
图4 点击“Draw selector on top”按钮后Item选中时的效果
![QQ截图20120514151519.png QQ截图20120514151519.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwMzh8YjkzNjVhMWZ8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
图5 点击“Draw selector as background”按钮后item选中的效果
2) Demo2
![QQ截图20120514152826.png QQ截图20120514152826.png](http://www.devdiv.com/forum.php?mod=attachment&aid=MzEwMzd8MTRkNmE2NTl8MTM5NTY0OTM1NHwwfDEyNDI1Nw%3D%3D&noupdate=yes)
http://www.devdiv.com/Android-listview_-thread-124257-1-1.html