您的位置:首页 > 移动开发 > Android开发

Android Adapter浅谈

2014-03-05 17:22 162 查看
本文部分内容启发于:http://www.cnblogs.com/allin/archive/2010/05/11/1732200.html

首先上类图:



一、在程序开发中,我们常用到ListView,使用ListView需要三个元素:

    1、ListView控件:展示每条数据的框架,是一个ViewGroup;

    2、Adapter适配器:连接数据与展示View的中介,通知ListView绘制多少View,每条View怎么绘制,View内部事件如何响应;

    3、数据:具体被映射的字符串、图片、状态等信息,有特定的格式要求,具体视Adapter实现而定;

二、适配器Adapter的基础功能定义在接口Adapter中,包括10个方法:

    1、registerDataSetObserver/unregisterDataSetObserver:注册/撤销Adapter关联数据发生变化的监察者,注册后可以完成重绘界面等操作;

    2、getCount:返回当前Adapter需要绘制多少条数据,ListView按返回值绘制N行View;

    3、getItem(int position):返回当前Adapter持有的对应position的数据,返回值对应ListView绘制的某一行展现的数据;

    4、getItemId(int position):返回当前Adapter对应位置的数据的唯一标识id。通常如果没有就返回position,如果是数据库内读出的数据就返回主键,如果item对应有int的唯一标识符就返回该值,该值如果设置不正确,可能会导致ListView的onItemClick内的操作对象不是你要处理的对象,比如你删某个item,却删了另一个,或者删除不起作用;

    5、hasStableIds:返回当前Adapter持有的数据实体们是否有稳定的唯一标识id,比如4中讲到的返回position是不稳定的,可能随着数据变化导致其值变化,此时需要          返回false,如果返回主键或者唯一标识符则该方法可以返回true。其作用是:返回true,ListView会依据4中的返回的id来确定当前显示哪条内容,也就是firstVisibleChild的位置。

    6、getView(int position, View convertView, ViewGroup parent):返回当前Adapter对应position应该绘制的View,这个View可以千奇百怪,需要配套getItemViewType使用,一个列表内所有数据展示都一模一样的就不需要了;

    7、getItemViewType(int position):返回当前Adapter对应position位置数据的View的类型,比如你第一行绘制了一个TextView,第二行绘制了一个ImageView,在ListView滚动的时候,第二行肯定不能复用来显示第一行应该显示的数据,这时候就让它们返回不同的ViewType,ListView控件就不会不识趣了,这里需要注意AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER
= -2 已经被占用了,ITEM_VIEW_TYPE_IGNORE = -1 也没了,所以我们用正数一般都没事;

    8、getViewTypeCount:返回当前Adapter持有多少种ViewType,6中返回了多少种,这里写总数就行;

    9、isEmpty:返回当前Adapter是否持有数据,官方文档说法是:它用于判断空的View是否需要展示,如果没有headers和footers,返回getCount==0就好了,否则可以自定义;

    这些里面我们一般情况下主要和getCount、getItem(int position)、getItemId(int position)、getView打交道,因为BaseAdapter已经实现了上面的其他方法;

三、Adapter的继承者包括ListAdapter和SpinnerAdapter,也是接口。

    1、
其中ListAdapter连接ListView和数据,增加了判断ListView中绘制的View是否是分隔符的接口,定义了两个方法:

        1)areAllItemsEnabled:返回true标识所有的item对应的View都是可点击可选择的,也就是没有分隔符;

        2)isEnabled(int position):返回true标识当前Adapter对应position位置数据不是一个分隔符,因为分隔符不可点击且不可选择;

    2、
SpinnerAdapter连接Spinner和数据,获取Spinner的下拉框内的显示控件,只定义了一个方法:

        1)getDropDownView(int position, View convertView, ViewGroup parent):返回当前Adapter对应position应该绘制的下拉框内的View;

四、扩充的接口往下就是我们比较熟悉的类BaseAdapter了,还有个扩展接口WrapperListAdapter。

    1、
我们先讲WrapperListAdapter,它定义了一个包裹另一个ListAdapter的类,增加了一个实现方法:

        1)getWrappedAdapter:返回被包裹的ListAdapter;

        在android系统内,系统只提供了它的一个实现类——HeaderViewListAdapter,也就是包含Header View和Footer View的ListAdapter,其内部维持了列表头尾的增加删除等操作,且封装了一层ListAdapter,从而为ListView提供列表头尾的管理与数据关联工作。该类还实现了Filterable接口,对外提供筛选功能(自动匹配功能,就是电话簿中输入号码查找出合适联系人的功能);

    2、
下面我们详细讲一下BaseAdapter,因为它是我们常用到的ArrayAdapter、SimpleAdapter、CursorAdapter和自定义Adapter的父亲。

        BaseAdapter非常简单,绝大部分都是接口的默认实现,它持有一个android.database.DataSetObservable的对象,通过该对象注册/注销数据观察者,并在接口之外提供了两个方法:

            1)notifyDataSetChanged:外部或内部调用通知数据观察者适配器内持有的数据发生了改变,view监听到该事件后可以刷新界面;

            2)notifyDataSetInvalidated:外部或内部调用通知数据观察者适配器内持有的数据已经失效,注意:按照官方解释,该方法一旦调用,这个Adapter就失效了,也就不能接着报告数据变化事件;

        这两个方法都调用DataSetObservable对象的方法实现;

        BaseAdapter的其他默认实现包括:

            1)hasStableIds 返回false,也就是默认itemId不稳定,如果我们有稳定id,可以覆写为true;

            2)areAllItemsEnabled 返回true,也就是默认没有分隔符,所有item都可点击可选择;

            3)isEnabled(int position) 返回true,同2;

            4)getDropDownView 调用了getView;

            5)getItemViewType(int position) 返回0,意味着默认情况下所有我们通过getView生成的View都是同类型的;

            6)getViewTypeCount 返回1,依据5;

            7)isEmpty 返回getCount==0,因为没有持有列表头尾;

五、BaseAdapter的继承者们就是我们做界面开发直接接触的类了,它们包括:ArrayAdapter<T>、SimpleAdapter、CursorAdapter和自定义Adapter。这里略过CursorAdapter,没有用过,以后看机会再补上。

    1、ArrayAdapter

        ArrayAdapter只能用于显示一行文字,用法一般如下:

mAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_spinner_item, getData());
mListView.setAdapter(mAdapter);
        该类内置add,insert, remove, clear等方法,实现了BaseAdapter没有搞定的4个方法,实现了自动匹配Filterable接口,还提供了两个额外的方法:

            1)setDropDownViewResource(int resource):供Spinner使用,设置下拉框显示字符串的View资源样式,也就是一个xml文件,文件里只能定义一个TextView的子类,不能加LinearLayout之类的,要注意。

            2)setNotifyOnChange(boolean notifyOnChange):设置为false可以阻止界面知道数据发生了变化,直到外部调用notifyDataSetChanged将mNotifyOnChange开关打开为止;

    2、SimpleAdapter

        SimpleAdapter相对而言比ArrayAdapter自由很多,我们可以用自己的定义的界面,可以展现很多种数据,包括选中状态、文字、图片等;它的缺点就是数据格式太死板,我们必须使用List<? extends Map<String, ?>>作为数据源,这个转化过程很没有美感,且耗时;

        android实现的SimpleAdapter默认只允许待显示对象为Checkable、TextView、ImageView的数据,用法一般如下:

            1)布局:

<?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="wrap_content"
android:orientation="horizontal" >

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF000000" />
</LinearLayout>

</LinearLayout>

            2)代码

package com.example.testlistview;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

public class MainActivity extends Activity {
private ListView mListView = null;
private BaseAdapter mAdapter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();

mAdapter = new SimpleAdapter(getApplicationContext(), getData(), R.layout.simple_adapter_demo,
new String[] {"text", "image", "checkable"},
new int[] {R.id.textView, R.id.imageView, R.id.checkBox});
mListView.setAdapter(mAdapter);

}

private List<? extends Map<String, ?>> getData() {
ArrayList<HashMap<String, Object>> list = new ArrayList<HashMap<String,Object>>();
HashMap<String, Object> map1 = new HashMap<String, Object>();
map1.put("text", "nihao");
map1.put("image", android.R.drawable.ic_secure);
map1.put("checkable", false);
list.add(map1);

HashMap<String, Object> map2 = new HashMap<String, Object>();
map2.put("text", "android");
map2.put("image", android.R.drawable.ic_delete);
map2.put("checkable", true);
list.add(map2);
return list;
}

private void initView() {
mListView = (ListView) findViewById(R.id.listview);
}

}


       3) 效果:



        当然,SimpleAdapter也不是只能显示这三种数据类型,它有个内部接口ViewBinder,只要自己实现一个ViewBinder的子类,并通过setViewBinder(ViewBinder viewBinder)赋值给SimpleAdapter,我们也可以定义对其他的控件进行赋值。这是SimpleAdapter的扩展性。

不过,说实话瑜不掩瑕,如果数据格式不是Map的,不建议用SimpleAdapter,费脑筋。

六、万能的自定义Adapter

    好了,我们为了讲解ListView的适配器Adapter,从Adapter接口往下讲了四层了,到了这里,就是故事的高潮部分。我们讲自定义Adapter,到目前为止,能装在ListView里面的东西只有Checkable、String和Bitmap,这明显不够用,但是android就给了这么点功能,所以我们就得自己动手DIY了。

    自定义Adapter,一般都继承BaseAdapter,因为BaseAdapter替我们实现了Adapter的基本功能,包括数据变化通知、部分接口实现等等,虽然ListView的setAdapter方法只要实现了ListAdapter接口的就可以用,但是有现成的,我们干嘛要再实现一遍那十几个接口呢,对吧!~

    我们先说一下自定义Adapter的好处:

        1)数据存储方式灵活:随便什么格式的,只要自己知道格式,就能往里面放;

        2)界面展现方式灵活:你可以任意布局摆弄数据,可以加自定义的分隔符,可以加按钮等等等等;

    好了,好处说完了就开始动手了。真写起来,其实自定义Adapter没有啥特别的,继承BaseAdapter,如前文所叙,里面有四个方法是需要我们搞定的,分别是:

        1)getCount(),返回传入我们定义的Adapter内的数据列表长度,用于通知ListView绘制多少行。

        2)getItem(int position),返回传入的数据列表的第position个数据。

        3)getItemId(int position),返回传入的数据列表的第position个数据对应的唯一标识符,强烈建议不要返回position。

        4)getView(int position, View convertView, ViewGroup parent),返回传入的数据列表的第position个数据对应在ListView的行内应该怎么绘制,我们甚至可以判断一下position,然后在第三行绘制一个ImageView,展现一个深情款款的大猪头。当然,如果其他各行都不是ImageView的话,我们一定要覆写getItemViewType(int position)和getViewTypeCount(),因为BaseAdapter默认每行类型都是0,总的TypeCount为1。

        这里有个两个需要注意的地方:

            1)convertView会为当前position带来一个可用的view,是给我们复用的,作用是当滚动导致ListView内某行View不可见时,直接复用该View来展示第position个数据,这样能大大减少内存使用量,不管要显示的数据是不是几千条,一般显示一行字的ListView最多创建10几个View就可以满足显示要求了,所以一定得用上。

            2)View有个setTag方法,而我们使用findViewById是要查找View树的,相对而言,创建一个内部类ViewHolder并设为View的Tag,会在复用convertView时直接提供对象引用(如TextView、ImageView等等),所以ViewHolder也应该用上。

        下面上代码:

            1)布局my_adapter_demo.xml

<?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="wrap_content"
android:orientation="horizontal" >

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >

<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF000000" />
</LinearLayout>

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除" />

</LinearLayout>

            2)数据体Data.java

package com.example.testlistview;

import android.graphics.Bitmap;

public class Data {
private int mId = -1;
private String mText = null;
private Bitmap mImage = null;
private boolean mIsChecked = false;

public Data(int mId, String mText, Bitmap mImage, boolean mIsChecked) {
super();
this.mId = mId;
this.mText = mText;
this.mImage = mImage;
this.mIsChecked = mIsChecked;
}

public int getId() {
return mId;
}

public void setId(int mId) {
this.mId = mId;
}

public String getText() {
return mText;
}

public void setText(String mText) {
this.mText = mText;
}

public Bitmap getImage() {
return mImage;
}

public void setImage(Bitmap mImage) {
this.mImage = mImage;
}

public boolean isChecked() {
return mIsChecked;
}

public void setIsChecked(boolean mIsChecked) {
this.mIsChecked = mIsChecked;
}

}

            3)自定义Adapter   MyAdapter.java

package com.example.testlistview;

import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MyAdapter extends BaseAdapter {
private static final String TAG = "MyAdapter";
private List<Data> mList = null;
private LayoutInflater mInflater = null;
private Context mContext = null;

public MyAdapter(Context context, List<Data> list) {
if(list == null) {
list =new ArrayList<Data>();
}
mList = list;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mContext = context;
}

@Override
public int getCount() {
return mList.size();
}

@Override
public Object getItem(int position) {
return mList.get(position);
}

@Override
public long getItemId(int position) {
//最好返回Data对应的唯一标识id
return mList.get(position).getId();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
Data data = mList.get(position);

View view = null;
ViewHolder holder = null;

if(null == convertView) {
view = mInflater.inflate(R.layout.my_adapter_demo, null);
holder = new ViewHolder();
holder.id = data.getId();
holder.textView = (TextView) view.findViewById(R.id.textView);
holder.imageView = (ImageView) view.findViewById(R.id.imageView);
holder.checkBox = (CheckBox) view.findViewById(R.id.checkBox);
holder.button = (Button) view.findViewById(R.id.button);
//将持有的控件引用存入View的Tag内
view.setTag(holder);
} else {
//复用convertView,提高ListView的效率,减少内存占用
view = convertView;
//读出控件引用
holder = (ViewHolder) view.getTag();
Log.d(TAG, holder.id + " is now display " + data.getId());
holder.id = data.getId();
}

holder.textView.setText(data.getText());
holder.imageView.setImageBitmap(data.getImage());
holder.checkBox.setChecked(data.isChecked());
holder.button.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
Toast.makeText(mContext, "按钮被点击了", Toast.LENGTH_SHORT).show();
}
});
return view;
}

@Override
public int getItemViewType(int position) {
if(position == 2) {
return 1;
}
return super.getItemViewType(position);
}

@Override
public int getViewTypeCount() {
return 2;
}

private final class ViewHolder {
int id = -1;
TextView textView = null;
ImageView imageView = null;
CheckBox checkBox = null;
Button button = null;
}

}

            4)主界面 MainActivity.java

package com.example.testlistview;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

public class MainActivity extends Activity {
private ListView mListView = null;
private BaseAdapter mAdapter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();

mAdapter = new MyAdapter(this, getData());
mListView.setAdapter(mAdapter);

}

private List<Data> getData() {
ArrayList<Data> list = new ArrayList<Data>();
Data data1 = new Data(1, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data1);

Data data2 = new Data(2, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data2);

Data data3 = new Data(3, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data3);

Data data4 = new Data(4, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data4);

Data data5 = new Data(5, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data5);

Data data6 = new Data(6, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data6);

Data data7 = new Data(7, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data7);

Data data8 = new Data(8, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data8);

Data data9 = new Data(9, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data9);

Data data10 = new Data(10, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data10);

Data data11 = new Data(11, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data11);

Data data12 = new Data(12, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data12);

Data data13 = new Data(13, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data13);

Data data14 = new Data(14, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data14);

Data data15 = new Data(15, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data15);

Data data16 = new Data(16, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data16);

return list;
}

private void initView() {
mListView = (ListView) findViewById(R.id.listview);
}

}


        通过这个例子我们可以验证,不同ViewType的View是不会作为convertView使用的,另外往下滚动时,上方不可见的View会被复用来显示下方将要显示出来的数据。

    好了,整个Android的ListView Adapter就介绍到这里,有后续需要注意的地方,我会接着这篇文章往下写的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: