作者:徐冉。文章首发在他的个人博客。 )
AdapterView&Adapter家族
adapterview就是和数据有关的控件,如listview,gridview,spinnerview等等,只要是在展示的过程中需要和数据交互的view都属于这一类。要使用adapterview,需要遵循三个步骤,这些在之前的博文也已经提到过:
- 创建数据源
- 创建adapter,并且将数据源和adapter绑定在一起
- 将装载好的adapter绑定到我们想要展示的控件上面。
数据源
一般来讲可能是string的数组用来存放一些文本信息,也可能是int型的数组用来存放资源的ID,甚至复杂一点的,比如list容器,里面的元素类型是一个map容器,还有我们手机通讯录中数据库文件中的信息。这些都可以作为合法的数据源来进行展示操作。
适配器
既然有不同的数据源,那么自然而然就需要不同的适配器来装载和配置这些数据。关于adapter,有两张神图,能让你一下子就明白。
第一张图片,很好的向你展示了数据源,适配器,目的控件三者之间是如何工作的以及他们的关系。第二张图,向我们展示了adapter家族内部分的继承关系。从图中我们可以看出,Adapter是最大的一个接口,其次被两个listadapter和spinneradapter所重写,到这里为止还都是接口而已。之后有了一个叫做BaseAdapter的抽象类来重写这些接口,从baseadater再向下,我们就会发现此时的arrayadapter , simpleadapter, cursoradapterz这三个类,才是我们在代码中正常能够通过实例化对象来使用的类。然而这三个adapter却各有分工。
- Arrayadapter:它所处理的数据源,主要是一个string类型的数组,因为我们在创建adapter实例并且绑定数据源的时候,我们会同时指定一个展示这些数据的布局,若要利用系统中比较简单的布局的话,只能显示一行字在textview上面。
- Simlpeadapter:为了满足我们自定义的需求,这种适配器绑定的数据源,可以在一定的限制范围内,任意的组织我们的原始数据结构,比如list<map<String,Object>>。
- Cursoradapter:主要用于数据库的内容
展示控件
Listview,Gridview, Spinner等等,属于adapterview类型的都可以。
AdapterView实例展示
以下实例AdapterView用的是ListView
ArrayAdapter
依然是严格按照前面的三个步骤来写代码:
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
|
package com.example.test; import android.app.Activity; import android.app.ListActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; public class MainActivity extends ListActivity { private ArrayAdapter<String>adapter; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); //setContentView(R.layout.activity_main); //1创建数据源 String[] items={ "haha" , "heihei" , "hehe" }; //2.创建adapter并且绑定数据源 adapter = new ArrayAdapter<String>( this , android.R.layout.simple_list_item_1, items); //3.将adapter绑定到view上面 setListAdapter(adapter); } } |
SimpleAdapter
simpleadapter给了我们很大的发挥空间,能够让我们自定义数据,自定义展示数据的样式。
首先,我们要定义一个展示我们每一条数据的xml布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<? xml version = "1.0" encoding = "utf-8" ?> < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "horizontal" > < ImageView android:layout_marginLeft = "20dp" android:layout_marginTop = "20dp" android:id = "@+id/imageView1" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:src = "@drawable/member" /> < TextView android:id = "@+id/textView1" android:layout_marginLeft = "20dp" android:layout_marginTop = "20dp" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "Large Text" android:textAppearance = "?android:attr/textAppearanceLarge" /> </ LinearLayout > |
这个布局文件,会显示一个图片,显示一个textview,上面将会替换成我们数据源中对应的内容。
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
|
package com.example.test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.app.ListActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.SimpleAdapter; public class MainActivity extends ListActivity { private SimpleAdapter madapter; private List<Map<String, Object>>list; private Map<String, Object>map; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); //setContentView(R.layout.activity_main); //1创建数据源 list = new ArrayList<Map<String,Object>>(); map = new HashMap<String, Object>(); map.put( "name" , "xuran" ); map.put( "img" , R.drawable.member); list.add(map); map = new HashMap<String, Object>(); map.put( "name" , "doubi" ); map.put( "img" , R.drawable.member); list.add(map); map = new HashMap<String, Object>(); map.put( "name" , "hehe" ); map.put( "img" , R.drawable.member); list.add(map); //2.创建adapter并且绑定数据源 madapter = new SimpleAdapter( this , list,R.layout.layout_simple, new String[]{ "name" , "img" }, new int []{R.id.imageView1,R.id.imageView1}); //3.将adapter绑定到view上面 setListAdapter(madapter); } } |
SimpleAdapter的实现相对来说复杂一些,无论是数据源的创建还是适配器的创建,都和之前的有所区别。 其中需要终点理解的就是adapter的创建并同时绑定数据的过程。这个函数的参数一共有四个:
- 表示adapter所在的上下文
- 数据源
- 我们要展示数据源的样式文件(xml的布局文件,可以用我们自定义的,也可以用系统提供的)
- 后面两个参数是两个数组,前面一个数组对应着数据源每一个元素中map容器的两个键值,后面一个数组代表了我们用于展示数据的布局文件中控件的id,意思就是每一个数据源中,和name键对应的值,放置在R.id.imageView1上面显示,和img键值对应的数据放置在R.id.imageView1上面,都是一个一一对应的数据映射关系。
这是一个比较复杂的adapter的使用过程,数据类型和适配器的创建都丰富了很多。
SimpleCursorAdapter
这个适配器的数据源和我们之前遇到过的不同,不是我们在程序中现生成的数据,而是读取的通讯录中的信息。手机中是有一个数据库的文件来存储我们通讯录中联系人的各种信息的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class MainActivity extends ListActivity { private SimpleCursorAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); //setContentView(R.layout.activity_main); //1创建数据源 //通过查询通讯录信息来获取数据源,在cursor对象中 Cursor cursor = getContentResolver().query(People.CONTENT_URI, null , null , null , null ); //2.创建adapter并且绑定数据源 adapter = new SimpleCursorAdapter( this , android.R.layout.simple_list_item_1, cursor, new String[]{People.NAME}, new int []{android.R.id.text1}); //3.将adapter绑定到view上面 setListAdapter(adapter); } } |
> 这里需要提出一点的是,因为我们在展示数据的的时候,用的是系统提供的布局文件,在这个布局文件中有一个textview,他的id是预设好了的text1。所以我们能够把通讯录中的人名信息写入到那个textview上面。 # 自定义Adapter与GetView函数 ## 为什么要自定义adapter 有的时候,我们可能不想用系统自带的adapter,因为能够实例化对象的adapter类都是从BaseAdapter这个抽象类继承而来,为了了解adapter的内部实现,我们当然可以自己定义的一个类继承自BaseAdapter的类,来实现我们自己的adapter。另一方面,我们可能会在显示一些数据的同时添加一些按钮,并且为他们映射指定的事件。如果有这样的需求,那么系统的adapter是不能够满足你的,我们可以把数据源中的数据通过adater映射到控件上,但是没办法把按钮的点击事件映射到对应的Button控件上。这样的话,我们就需要深入了解一下listview中每一个item是怎么形成的。 自定义布局文件
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
|
<? xml version = "1.0" encoding = "utf-8" ?> < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "horizontal" > < ImageView android:id = "@+id/stu_imageView" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginLeft = "10dp" android:layout_marginTop = "25dp" android:src = "@drawable/member" /> < LinearLayout android:layout_width = "205dp" android:layout_height = "wrap_content" android:layout_marginLeft = "25dp" android:layout_marginTop = "15dp" android:orientation = "vertical" > < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "5dp" android:orientation = "horizontal" > < TextView android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "学生ID " /> < TextView android:id = "@+id/stu_id" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "1" /> </ LinearLayout > < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "5dp" android:orientation = "horizontal" > < TextView android:id = "@+id/name_tag" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "学生姓名 " /> < TextView android:id = "@+id/stu_name" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginLeft = "15dp" android:text = "徐冉" /> </ LinearLayout > < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "5dp" android:orientation = "horizontal" > < TextView android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "学生年龄 " /> < TextView android:id = "@+id/stu_age" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "1" /> </ LinearLayout > </ LinearLayout > < CheckBox android:id = "@+id/stu_checkBox" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginRight = "10dp" android:layout_marginTop = "15dp" /> </ LinearLayout > |
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
public class MainActivity extends ListActivity { private ListView mListView; // view private MyAdapter mAdapter; // adapter private List<Map<String, Object>> itemsource; // data source private Map<String, Object> mmap; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // 1创建数据源 itemsource = new ArrayList<Map<String,Object>>(); itemsource = getData(); // 2创建adapter mAdapter = new MyAdapter( this ); // 3绑定adapter到listview setListAdapter(mAdapter); } private List<Map<String, Object>> getData() { // load data mmap = new HashMap<String, Object>(); mmap.put( "id" , "1" ); mmap.put( "name" , "徐冉" ); mmap.put( "age" , "21" ); mmap.put( "img" , R.drawable.currency_icon_chf); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "2" ); mmap.put( "name" , "大黄" ); mmap.put( "age" , "15" ); mmap.put( "img" , R.drawable.currency_icon_cny); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "3" ); mmap.put( "name" , "大史" ); mmap.put( "age" , "23" ); mmap.put( "img" , R.drawable.currency_icon_dkk); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "4" ); mmap.put( "name" , "二轩" ); mmap.put( "age" , "28" ); mmap.put( "img" , R.drawable.currency_icon_eur); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "5" ); mmap.put( "name" , "干顺" ); mmap.put( "age" , "21" ); mmap.put( "img" , R.drawable.currency_icon_sek); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "6" ); mmap.put( "name" , "赵龙" ); mmap.put( "age" , "21" ); mmap.put( "img" , R.drawable.currency_icon_nzd); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "7" ); mmap.put( "name" , "富美" ); mmap.put( "age" , "18" ); mmap.put( "img" , R.drawable.currency_icon_myr); itemsource.add(mmap); mmap = new HashMap<String, Object>(); mmap.put( "id" , "8" ); mmap.put( "name" , "小灰灰" ); mmap.put( "age" , "91" ); mmap.put( "img" , R.drawable.currency_icon_nok); itemsource.add(mmap); return itemsource; } public class ViewHolder { TextView mid; TextView mname; TextView mage; ImageView mimg; CheckBox mcheck; } public class MyAdapter extends BaseAdapter { private LayoutInflater inflater; private ViewHolder holder; public MyAdapter(Context context) { // Obtains the LayoutInflater from the given context. inflater = LayoutInflater.from(context); holder = new ViewHolder(); // TODO Auto-generated constructor stub } @Override public int getCount() { // TODO Auto-generated method stub return itemsource.size(); } @Override public Object getItem( int position) { // TODO Auto-generated method stub return itemsource.get(position); } @Override public long getItemId( int position) { // TODO Auto-generated method stub return position; } @Override public View getView( int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub if (convertView == null ) { convertView = inflater.inflate(R.layout.listview_item, null ); holder.mid = (TextView) convertView.findViewById(R.id.stu_id); holder.mname = (TextView) convertView .findViewById(R.id.stu_name); holder.mage = (TextView) convertView .findViewById(R.id.stu_age); holder.mcheck = (CheckBox) convertView .findViewById(R.id.stu_checkBox); holder.mimg = (ImageView) convertView .findViewById(R.id.stu_imageView); convertView.setTag(holder); } else holder = (ViewHolder) convertView.getTag(); //投放数据 holder.mid.setText((String) itemsource.get(position).get( "id" )); holder.mname .setText((String) itemsource.get(position).get( "name" )); holder.mage.setText((String) itemsource.get(position).get( "age" )); holder.mimg.setImageResource((Integer) itemsource.get(position).get( "img" )); return convertView; } } } |
使用自定义的adapter和使用系统提供的adapter步骤是一样的,在这里就不多说了。首先主要说一下我们自己实现的adapter类,以及listview是怎么形成。
自己实现adapter类
通过继承BaseAdapter来实现我们自己的adapter类,同时我们必须重写baseadapter里面所提供的成员方法。为什么我们要把这个类定义在Activity的类中,是因为如果我们再定义一个单独的类的话,adapter这个类中需要用到Activity类中的一些数据,传递起来很麻烦,还有一个原因就是,这个类本来也就是在这个activity中用,所以定义为一个内部类是比较方便的。
ListView是怎么形成的
listview在开始绘制之前,首先要调用getcount这个函数,这个函数返回了我们要在这个Listview上面显示多少个item,有了这个数据我们才能够继续绘制Listview. 然后根据这个长度开始调用getview函数进行绘制listview中的每一行。
重中之重—GetView函数详解
我们一般在给一个activity设定一个视图的时候,都用的是setcontentview这个方法来直接指定布局文件,但是activity中早已经内置了指定视图的工具—-LayoutInflater.这个工具就像是一个压力泵,能够把布局文件压缩成一个视图,呈现出来。它的作用类似于 findViewById(), 不同点是LayoutInflater是用来找layout下xml布局文件,并且实例化!而findViewById()是找具体xml下的具体 widget控件.
函数一共有三个参数,position标识我们现在正在绘制listview中第几个item,converview相当于一个view控件的缓存装置,它将我们定义好显示没一行item的布局文件压缩成一个视图,布局中的部分view都在它里面。在创建adapter的时候,通过adapter的构造函数,定义了一个LayoutInfalter,并且获取到当前activity的LayoutInflater。之后,通过inflater压缩xml文件形成一个视图,赋值convertview。因为一个布局文件中所有的控件展示,都是一个item,因为布局中所有的控件应该以一个整体出现。所以,我们定义了一个class–>ViewHolder,里面的成员就是我们没一行item布局文件中的控件集合。通过findviewbyid我们找到了布局当中的每一个view,并且最后把相应的数据投放在view上面予以显示。
细心观察的人会发现,getview函数里面有两句话貌似作用不是很明确:
1
2
|
convertView.setTag(holder); holder = (ViewHolder) convertView.getTag(); |
我们都知道Listview中有很多的item,数量少的话,我们每次在绘制listview的每一行的时候,都需要重新findviewbyid来查找并且创建一系列item里面所需要的view。那么如果我们的listview有几亿个item呢,不但findviewbyid会花费大量的时间,如果每一个item都重新inflate的话,那我们的内存空间也受不了。实际上Android为我们提供了一套重复利用的机制叫做“Recycler”。
在一个完整的ListView第一次出现时,每个Item都是Null的,listview创建的item数,只能够充满屏幕为止。假设一屏上最多显示7个item。当我们滑动Listview的时候,旧的item滑出去,新的item滑进来,那么这个滑进来滑出去的过程,在程序的内部是怎么实现的呢。
先来看张图:
我之前在前面说到,convertview相当于一个缓存器,在程序刚开始运行,第一屏listview的所有item出现在屏幕中时,convertView为零。随后,如果我们向上滑动,item1滑出屏幕,item8滑入屏幕,此时convertview将之前滑出去的视图的信息记录下来,当有新的item滑进来,如果他们所用的都是同一个xml布局文件压缩成的view视图,那么我们就不再去重新inflate一个视图,直接利用刚刚滑出去的那个item的视图,更新一下数据就可以了。这样的话,就能够实现重复利用,不用再多花费时间和空间。
当item2也滑出去的时候,item9滑进来,item9得到的将是item2之前的视图,然后更新一下数据即可。以后一直按照这样的规律:item10—item3, item11—item4。
虽然我们已经重复利用了之前绘制的视图,但是在更新数据之前,我们总得通过findviewbyid来找到相应的控件进行数据更新操作。为了能够再次优化这一部分,我们在第一屏listview的item创建的时候,就执行settag函数,它为每一个视图绑定一个viewholder对象,每一个item视图对应这么一个viewholder对象,当第一屏的listview绘制完成的时候,每一个视图都携带一个查找完成的viewholder对象,这个viewholder对象的成员里面已经存好了对应视图内的控件(如textview,button)。当convertView不为0时(已经开始滑动),重复利用已经创建的view视图的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。
可以在getview函数中打出相应的log信息来观察规律
总结
Adapter和AdapterView还是android中比较复杂的部分,像listview就是一个很好的例子,listview还有很多优化的方式,在面试中也经常会考到,如果不优化,很可能就会造成卡顿的现象让用户的体验不好。我一开始就错在加了gettag函数没有加settag函数,因为当时并不明白这是什么意思。所以学知识,还是要保证一个强烈的求知欲,不能为了完成任务就去copy一些代码,效果是出来了,但是原理你不明白,下次碰到变形的情况还是不会,如果一旦明白了原理,说不准在会的基础上,还能够加以改进,得到得到更好的效果。