[Android] ListView中getView的原理+如何在ListView中放置多個item
傳送門http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html
ListView 和 Adapter 的基礎
工作原理:
- ListView 針對List中每個item,要求 adapter “給我一個視圖” (getView)。
- 一個新的視圖被返回並顯示
如果我們有上億個項目要顯示怎麼辦?爲每個項目創建一個新視圖?NO!這不可能!
實際上Android爲你緩存了視圖。
Android中有個叫做Recycler的構件,下圖是他的工作原理:
- 如果你有10億個項目(item),其中只有可見的項目存在內存中,其他的在Recycler中。
- ListView先請求一個type1視圖(getView)然後請求其他可見的項目。convertView在getView中是空(null)的。
- 當item1滾出屏幕,並且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數據然後返回convertView,不必重新創建一個視圖。
請看下面的示例代碼,這裏在getView中使用了System.out進行輸出
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
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
57
58
59
60
61
62
63
64
65
|
public
class MultipleItemsList extends
ListActivity { private
MyCustomAdapter mAdapter; @Override public
void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); mAdapter =
new MyCustomAdapter(); for
( int
i = 0 ; i <
50 ; i++) { mAdapter.addItem( "item "
+ i); } setListAdapter(mAdapter); } private
class MyCustomAdapter
extends BaseAdapter { private
ArrayList mData = new
ArrayList(); private
LayoutInflater mInflater; public
MyCustomAdapter() { mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public
void addItem( final
String item) { mData.add(item); notifyDataSetChanged(); } @Override public
int getCount() { return
mData.size(); } @Override public
String getItem( int
position) { return
mData.get(position); } @Override public
long getItemId( int
position) { return
position; } @Override public
View getView( int
position, View convertView, ViewGroup parent) { System.out.println( "getView "
+ position + " "
+ convertView); ViewHolder holder =
null ; if
(convertView == null ) { convertView = mInflater.inflate(R.layout.item1,
null ); holder =
new ViewHolder(); holder.textView = (TextView)convertView.findViewById(R.id.text); convertView.setTag(holder); }
else { holder = (ViewHolder)convertView.getTag(); } holder.textView.setText(mData.get(position)); return
convertView; } } public
static class
ViewHolder { public
TextView textView; } } |
執行程序,然後在Logcat中查看日誌
getView 被調用 9 次 ,convertView 對於所有的可見項目是空值(如下)
02 - 05
13 : 47 : 32.559 : INFO/System.out( 947 ):
getView 0 null 02 - 05
13 : 47 : 32.570 : INFO/System.out( 947 ):
getView 1 null 02 - 05
13 : 47 : 32.589 : INFO/System.out( 947 ):
getView 2 null 02 - 05
13 : 47 : 32.599 : INFO/System.out( 947 ):
getView 3 null 02 - 05
13 : 47 : 32.619 : INFO/System.out( 947 ):
getView 4 null 02 - 05
13 : 47 : 32.629 : INFO/System.out( 947 ):
getView 5 null 02 - 05
13 : 47 : 32.708 : INFO/System.out( 947 ):
getView 6 null 02 - 05
13 : 47 : 32.719 : INFO/System.out( 947 ):
getView 7 null 02 - 05
13 : 47 : 32.729 : INFO/System.out( 947 ):
getView 8 null |
然後稍微向下滾動List,直到item10出現:
convertView仍然是空值,因爲recycler中沒有視圖(item1的邊緣仍然可見,在頂端)
02 - 05
13 : 48 : 25.169 : INFO/System.out( 947 ):
getView 9 null |
再滾動List
convertView不是空值了!item1離開屏幕到Recycler中去了,然後item11被創建
02 - 05
13 : 48 : 42.879 : INFO/System.out( 947 ):
getView 10 android.widget.LinearLayout @437430f8 |
再滾動:
02 - 05
14 : 01 : 31.069 : INFO/System.out( 947 ):
getView 11 android.widget.LinearLayout @437447d0 02 - 05
14 : 01 : 31.142 : INFO/System.out( 947 ):
getView 12 android.widget.LinearLayout @43744ff8 02 - 05
14 : 01 : 31.279 : INFO/System.out( 947 ):
getView 13 android.widget.LinearLayout @43743fa8 02 - 05
14 : 01 : 31.350 : INFO/System.out( 947 ):
getView 14 android.widget.LinearLayout @43745820 02 - 05
14 : 01 : 31.429 : INFO/System.out( 947 ):
getView 15 android.widget.LinearLayout @43746048 02 - 05
14 : 01 : 31.550 : INFO/System.out( 947 ):
getView 16 android.widget.LinearLayout @43746870 02 - 05
14 : 01 : 31.669 : INFO/System.out( 947 ):
getView 17 android.widget.LinearLayout @43747098 02 - 05
14 : 01 : 31.839 : INFO/System.out( 947 ):
getView 18 android.widget.LinearLayout @437478c0 02 - 05
14 : 03 : 30.900 : INFO/System.out( 947 ):
getView 19 android.widget.LinearLayout @43748df0 02 - 05
14 : 03 : 32.069 : INFO/System.out( 947 ):
getView 20 android.widget.LinearLayout @437430f8 |
convertView 如我們所期待的非空了,在item11離開屏幕之後,它的視圖(@437430f8)作爲convertView容納item21了
不同的項目佈局(item layout)
我們再舉一個稍微複雜的例子,在上例的list中加入一些分隔線
你需要做這些:
- 重(@Override)寫 getViewTypeCount() – 返回你有多少個不同的佈局
- 重寫 getItemViewType(int) – 由position返回view type id
- 根據view item的類型,在getView中創建正確的convertView
以下是代碼:
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
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
public
class MultipleItemsList extends
ListActivity { private
MyCustomAdapter mAdapter; @Override public
void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); mAdapter =
new MyCustomAdapter(); for
( int
i = 1 ; i <
50 ; i++) { mAdapter.addItem( "item "
+ i); if
(i % 4
== 0 ) { mAdapter.addSeparatorItem( "separator "
+ i); } } setListAdapter(mAdapter); } private
class MyCustomAdapter
extends BaseAdapter { private
static final
int TYPE_ITEM = 0 ; private
static final
int TYPE_SEPARATOR = 1 ; private
static final
int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1 ; private
ArrayList mData = new
ArrayList(); private
LayoutInflater mInflater; private
TreeSet mSeparatorsSet = new
TreeSet(); public
MyCustomAdapter() { mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public
void addItem( final
String item) { mData.add(item); notifyDataSetChanged(); } public
void addSeparatorItem( final
String item) { mData.add(item); // save separator position mSeparatorsSet.add(mData.size() -
1 ); notifyDataSetChanged(); } @Override public
int getItemViewType( int
position) { return
mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM; } @Override public
int getViewTypeCount() { return
TYPE_MAX_COUNT; } @Override public
int getCount() { return
mData.size(); } @Override public
String getItem( int
position) { return
mData.get(position); } @Override public
long getItemId( int
position) { return
position; } @Override public
View getView( int
position, View convertView, ViewGroup parent) { ViewHolder holder =
null ; int
type = getItemViewType(position); System.out.println( "getView "
+ position + " "
+ convertView + " type = "
+ type); if
(convertView == null ) { holder =
new ViewHolder(); switch
(type) { case
TYPE_ITEM: convertView = mInflater.inflate(R.layout.item1,
null ); holder.textView = (TextView)convertView.findViewById(R.id.text); break ; case
TYPE_SEPARATOR: convertView = mInflater.inflate(R.layout.item2,
null ); holder.textView = (TextView)convertView.findViewById(R.id.textSeparator); break ; } convertView.setTag(holder); }
else { holder = (ViewHolder)convertView.getTag(); } holder.textView.setText(mData.get(position)); return
convertView; } } public
static class
ViewHolder { public
TextView textView; } } |
運行程序,你會看到每4個item一個分割線
看看日誌,無異常,所有的convertView都是空的
02 - 05
15 : 19 : 03.080 : INFO/System.out( 1035 ):
getView 0 null
type = 0 02 - 05
15 : 19 : 03.112 : INFO/System.out( 1035 ):
getView 1 null
type = 0 02 - 05
15 : 19 : 03.130 : INFO/System.out( 1035 ):
getView 2 null
type = 0 02 - 05
15 : 19 : 03.141 : INFO/System.out( 1035 ):
getView 3 null
type = 0 02 - 05
15 : 19 : 03.160 : INFO/System.out( 1035 ):
getView 4 null
type = 1 02 - 05
15 : 19 : 03.170 : INFO/System.out( 1035 ):
getView 5 null
type = 0 02 - 05
15 : 19 : 03.180 : INFO/System.out( 1035 ):
getView 6 null
type = 0 02 - 05
15 : 19 : 03.190 : INFO/System.out( 1035 ):
getView 7 null
type = 0 02 - 05
15 : 19 : 03.210 : INFO/System.out( 1035 ):
getView 8 null
type = 0 02 - 05
15 : 19 : 03.210 : INFO/System.out( 1035 ):
getView 9 null
type = 1 |
滾動list:
02 - 05
15 : 19 : 54.160 : INFO/System.out( 1035 ):
getView 10 null
type = 0 02 - 05
15 : 19 : 57.440 : INFO/System.out( 1035 ):
getView 11 android.widget.LinearLayout @43744528
type = 0 02 - 05
15 : 20 : 01.310 : INFO/System.out( 1035 ):
getView 12 android.widget.LinearLayout @43744eb0
type = 0 02 - 05
15 : 20 : 01.880 : INFO/System.out( 1035 ):
getView 13 android.widget.LinearLayout @437456d8
type = 0 02 - 05
15 : 20 : 02.869 : INFO/System.out( 1035 ):
getView 14 null
type = 1 02 - 05
15 : 20 : 06.489 : INFO/System.out( 1035 ):
getView 15 android.widget.LinearLayout @43745f00
type = 0 02 - 05
15 : 20 : 07.749 : INFO/System.out( 1035 ):
getView 16 android.widget.LinearLayout @43747170
type = 0 02 - 05
15 : 20 : 10.250 : INFO/System.out( 1035 ):
getView 17 android.widget.LinearLayout @43747998
type = 0 02 - 05
15 : 20 : 11.661 : INFO/System.out( 1035 ):
getView 18 android.widget.LinearLayout @437481c0
type = 0 02 - 05
15 : 20 : 13.180 : INFO/System.out( 1035 ):
getView 19 android.widget.LinearLayout @437468a0
type = 1 02 - 05
15 : 20 : 16.900 : INFO/System.out( 1035 ):
getView 20 android.widget.LinearLayout @437489e8
type = 0 02 - 05
15 : 20 : 25.690 : INFO/System.out( 1035 ):
getView 21 android.widget.LinearLayout @4374a8d8
type = 0 |
convertView對於分割線是空的,直到第一個分割線可見,當其離開屏幕,視圖去到Recycler並且convertView開始起作用。
加深理解:http://www.cnblogs.com/wangjianhui/archive/2011/06/15/2081714.html