自定义Adapter及其实例
Android界面中有时候需要显示稍微复杂的界面时,就需要我们自定义一个adapter,而此adapter就要继承BaseAdapter,重新其中的方法.
Android中Adapter类其实就是把数据源绑定到指定的View上,然后再返回该View,而返回来的这个View就是ListView中的某一行item。这里返回来的View正是由我们的Adapter中的getView方法返回的。这样就会容易理解数据是怎样一条一条显示在ListView中的。
在完成这篇文章中的例子之后,我思考了很长时间,关于重写一个adapter,这其中真的有很多讲究,遇到一处不懂的都会查阅很长时间,但也不能保证我已经把其中的重中之重已经找完了,只要你想延伸都可以发现其中的无限.....
问题1:为什么我们要重写一个adapter?
问题2:android中这么多adapter,什么情况下该重写哪一个adapter(ArrayAdapter/SimpleAdapter/SimpleCursorAdapter...)?
问题3:我们重写的adapter为什么是一个内部类,是否建议把adapter做成一个内部类?
问题4:理解应用中的数据源一般都会用一个Map类型的List,有何意途?
问题5:通过adapter是怎样做到把一条一条的item放到ListView中的?
问题6:理解重写adapter时,重写的几个方法(getCount()/getItem()/getItemId()/getView())?
问题7:理解ListView使用adapter的机制
案例概览步骤: 1)创建2个layout,一个是界面顶部的显示,一个是ListView中的内容 1.1)ListView_header.xml(注:自我感觉界面中的控件的位置摆放与layout_width/layout_height/orientation/gravity/layout_gravity的属性设置关系非常大,一定要注意每个属性的用法) <?xml version="1.0" encoding="UTF-8"?> <!-- 第一个LinearLayout,充当父容器,要包揽两行,因此设置其 android:orientation为 纵向--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <!-- 第二个LinearLayout android:gravity 表示该LinearLayout中的类容相对于该LinearLayout 水平居中 android:layout_gravity 表示的是该LinearLayout相对于它上面的父容器(这里是第一个LinearLayout)水平居中 --> <LinearLayout android:id="@+id/toprow" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_horizontal"> <TextView android:id="@+id/quna" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff9966ff" android:textSize="25dp" android:text="@string/places"/> <!-- 用一个图片按钮 --> <ImageButton android:id="@+id/goBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/quna"/> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/placesList" android:textColor="#ff9966ff" android:textSize="20dp"/> <ListView android:id="@+id/list_places" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout> 1.2)listview_item.xml <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" //此属性的设置非常重要,决定里面的控件横向摆放 android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- android:layout_margin设置图片与旁边文章的距离 --> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp"/> <LinearLayout android:orientation="vertical" //也很重要决定里面的两个textview竖着放 android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff3399ff" android:textSize="20dp"/> <TextView android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff3399ff" android:textSize="13dp"/> </LinearLayout> <!-- 注意此处button的属性android:layout_gravity的用法 ,这里设置了都没效果 这里不是很理解为什么要在加一个LinearLayout,里面的button才可以居右?? 而且LinearLayout的width必须为fill_parent才可以 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <Button android:id="@+id/viewBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_select" android:layout_gravity="bottom|right"/> </LinearLayout> </LinearLayout> 2)、写java class,开发activity(建一个class继承Activity,添加click事件的时候还要实现onClickListener接口) 2.1) 重写onCreate方法-----------------------------------------------> private ListView listView; private ImageButton goBtn; private List<Map<String, Object>> testData; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.listview_header);//这里运行该项目的时候,让其显示listview_header.xml界面(layout),然后再将listview要显示的item项加到里面 //获得listview_header.xml中的ListView控件 listView = (ListView) findViewById(R.id.list_places); //获得imageButton给它添加click事件 goBtn = (ImageButton) findViewById(R.id.goBtn); goBtn.setOnClickListener(new OnClickListener() { //这里只是做出了简单的弹出框 @Override public void onClick(View v) { new AlertDialog.Builder(BaseAdapterTest2.this).setTitle("想去的国家:") .setMessage("i want to go France") .setPositiveButton("确定", null).sho(); } }); //数据源 testData = buildData(); MyAdapter adapter = new MyAdapter(this); //通过setAdapter而把数据绑定到ListView中 listView.setAdapter(adapter); } 2.2)创建数据,注意这里为什么要是Map类型的List集合 private List<Map<String, Object>> buildData(){ List<Map<String, Object>> data = new ArrayList<Map<String,Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("title", "中国"); map.put("info", "China"); map.put("image", R.drawable.p6); data.add(map); map = new HashMap<String, Object>(); map.put("title", "英国"); map.put("info", "England"); map.put("image", R.drawable.p7); data.add(map); map = new HashMap<String, Object>(); map.put("title", "美国"); map.put("info", "America"); map.put("image", R.drawable.p9); map = new HashMap<String, Object>(); map.put("title", "荷兰"); map.put("info", "Dutch"); map.put("image", R.drawable.p9); data.add(map); map = new HashMap<String, Object>(); map.put("title", "新西兰"); map.put("info", "New Zealand"); map.put("image", R.drawable.p7); data.add(map); data.add(map); return data; } 2.3)写自定义adapter(它是一个内部类,继承自BaseAdapter,并不确定是否自定义的adapter一般都是继承它),尤其注意其中的几个方法 public class MyAdapter extends BaseAdapter{ private LayoutInflater inflater;//这个一定要懂它的用法及作用 //构造函数:要理解(这里构造方法的意义非常强大,你也可以传一个数据集合的参数,可以根据需要来传参数) public MyAdapter(Context context){ this.inflater = LayoutInflater.from(context); } //这里的getCount方法是程序在加载显示到ui上时就要先读取的,这里获得的值决定了listview显示多少行 @Override public int getCount() { //在实际应用中,此处的返回值是由从数据库中查询出来的数据的总条数 return testData.size(); } //根据ListView所在位置返回View @Override public Object getItem(int position) { // TODO Auto-generated method stub return this.testData.get(position); } //根据ListView位置得到数据源集合中的Id @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } //重写adapter最重要的就是重写此方法,此方法也是决定listview界面的样式的 @Override public View getView(int position, View convertView, ViewGroup parent) { //有很多例子中都用到这个holder,理解下?? ViewHolder holder = null; //思考这里为何要判断convertView是否为空 ?? if(convertView == null){ holder = new ViewHolder(); //把vlist layout转换成View【LayoutInflater的作用】 convertView = inflater.inflate(R.layout.vlist, null); //通过上面layout得到的view来获取里面的具体控件 holder.image = (ImageView) convertView.findViewById(R.id.image); holder.title = (TextView) convertView.findViewById(R.id.title); holder.info = (TextView) convertView.findViewById(R.id.info); holder.viewBtn = (Button) convertView.findViewById(R.id.viewBtn); //不懂这里setTag什么意思?? convertView.setTag(holder); } else{ holder = (ViewHolder) convertView.getTag(); } //这里testData.get(position).get("title1")),其实就是从list集合(testData)中取出对应索引的map,然后再根据键值对取值 holder.image.setBackgroundResource((Integer) testData.get(position).get("image1")); holder.title.setText((String) testData.get(position).get("title1")); holder.info.setText((String) testData.get(position).get("info1")); //为listview上的button添加click监听 holder.viewBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ..... } }); return convertView; } } 到此,整个案例的关键部分已经全部出来了,做的过程中有很多地方没有想通也都做有注释,做完以后,多揣摩了几遍才将就理解,嘿嘿 重点提出一个疑问:不能确定是否在onCreate方法中的 new Adapter,在里面表面上看是只调用了一次而进入自定义Adapter中调用(其实也没有直接调用,自己想的它可能有一个自己的内部机制,每new完一个Adapter就直接调用getCount/getView方法吗)它里面的方法 ,调用一次就会绘制一个ListView中的一个item项,那么有很多条item的时候,它是否要那样循环调用很多次呢?? getView方法中的参数,convertview那块。在初始化的时候,每显示一行item项都会调用一次getView方法但每次调用的时候convertview为null(因为还没有旧的view);如果屏幕移动了之后,并且导致某些item项跑到屏幕外面,此时如果还有新的item需要产生,则这些item显示时调用的getView方法中的convertview就不为null,而是那些移出到屏幕之外的view,我们所要做的就是将需要显示的item项填充到移除屏幕外的(旧的)view中去,注意【convertview为null的不仅仅是初始化显示的那些item,还有一些是已经开始移入屏幕但还没有view被回收的那些item】。
运行结果: