android适配器模式设计与实现-BaseAdapter解析
摘要 适配器模式是一种重要的设计模式,在android中得到了广泛的应用。适配器类似于现实世界里面的插头,通过适配器,我们可以将分属于不同类的两种不同类型的数据整合起来,而不必去根据某一需要增加或者修改类里面的方法。 适配器又分为单向适配器和双向适配器
适配器模式是一种重要的设计模式,在android中得到了广泛的应用。适配器类似于现实世界里面的插头,通过适配器,我们可以将分属于不同类的两种不同类型的数据整合起来,而不必去根据某一需要增加或者修改类里面的方法。
适配器又分为单向适配器和双向适配器,在android中前者使用的比较频繁。比较常见的实现方式是:首先定义一个适配类,内部定义一个私有的需要适配的对象,该类提供一个构造函数,将该对象的一个实例作为参数传入,并在构造函数里面进行初始化,再提供一个公有的方法,返回另外一个需要适配的类所需要的数据类型。这样通过创建一个额外的类,专门负责数据类型的转换,在不改动原有类的前提下实现了所需的功能。这种设计模式提供了更好的复用性和可扩展性,尤其在我们无法获修改其中一个类或者类与类之间有比较多的不同类型的数据需要进行适配的时候显得格外重要。
在android中常见的适配器类有:BaseAdapter、SimpleAdapter等,首先我们看看android应用层是如何使用适配器的:
以listview为例,我们设计一个简单的适配,效果如下:
新建工程创建一个名为AdapterTest的Activity,在main.xml里创建一个listview内容如下:
<? xml version = "1.0" encoding = "utf-8" ?>
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical" android:layout_width = "fill_parent"
android:layout_height = "fill_parent" >
< ListView android:id = "@+id/min" android:layout_height = "wrap_content"
android:layout_width = "wrap_content" />
</ LinearLayout >
新建一个名为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 = "wrap_content" >
< ImageView android:layout_width = "wrap_content"
android:layout_height = "wrap_content" android:id = "@+id/malone" />
< TextView android:layout_width = "wrap_content"
android:layout_height = "wrap_content" android:layout_gravity = "center_vertical"
android:id = "@+id/malone1" />
</ LinearLayout >
在AdapterTest类中定义两个静态全局数组,里面存放我们要显示的内容
public static String\[\] mTitles = { "1" , "2" , "3" , "4" };
public static int \[\] mIds = { R.drawable. icon , R.drawable. icon ,
R.drawable. icon ,
R.drawable. icon };
创建一个内部适配类MinAdapter继承BaseAdapter,需要实现四个方法:
getCount、getItem、getItemId、getView
我们先定义两个私有的数组,并在构造函数中初始化:
private String\[\] titles ;
private int \[\] ids ;
public MinAdapter(String\[\] t, int \[\] idres) {
titles = t;
ids = idres;
}
实现其中的getCount和getView方法如下:
public int getCount () {
// TODO Auto-generated method stub
return titles . length ;
}
该回调方法返回listview中显示的行数
public View getView( int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater l = LayoutInflater.from (AdapterTest. this );
View v = l.inflate(R.layout. item , null );
ImageView itemImage = (ImageView)v.findViewById(R.id. malone );
itemImage.setBackgroundResource( ids \[position\]);
((TextView)v.findViewById(R.id. malone1 )).setText( titles \[position\]);
return v;
}
getView是一个比较重要的回调方法,它返回position位置上的view。
在oncreate中设置adapter:
setContentView(R.layout. main );
ListView list = (ListView)findViewById(R.id. min );
MinAdapter adapter = new MinAdapter( mTitles , mIds );
list.setAdapter(adapter);
这样就实现了一个简单的adapter。
接下来我们对BaseAdapter的实现源码进行分析:
BaseAdapter是一个抽象类,实现了了ListAdapter 和SpinnerAdapter两个接口,这两个接口都继承自Adapter接口。在这个接口中申明了我们需要实现的四个重要的方法。
接下来,我们进入ListView中查看setAdapter方法,ListView就是通过调用这个方法与适配器联系起来的。该方法的入参数为ListAdapter类型,ListAdapter同样继承了adapter,也就是说,我们只能够重写adapter中的一些回调方法才会起效。该方法中,首先会调用getCount方法来设置listitem的数目,进行一系列操作之后会调用requestLayout方法。由于ListView中并没有定义该方法,它会调用它的父类AbsListView中的requestLayout方法,该方法调用后会回调其中的onLayout方法,该方法会调用ListView中的layoutChildren方法,该方法会调用fillSpecific方法,fillSpecific方法,通过调用makeAndAddView方法得到需要view,然后将view放入list。查看makeAndAddView方法,它会调用父类的obtainView方法,而该方法会调用适配器中重写的getView方法。
child = mAdapter.getView(position, scrapView, this);或
child = mAdapter.getView(position,null, this);
另外一个比较常见的设置adapter的控件是Gallery,Gallery继承自AbsSpinner,该类内部提供了setAdapter方法,该方法的实现与listview类似,
通过 mItemCount= mAdapter.getCount();得到item的数量
调用requestLayout();方法回掉onLayout方法,调用layout方法,该方法中调用makeAndAddView方法,里面可以找到我们熟悉的语句:
child = mAdapter.getView(position, null, this);
综合listview和gallery,发现它们有着类似的实现过程,在setAdapter里面获取适配的item的个数,然后通知各自的控件构造这些item,构造的时候会通过适配器来获取需要适配的view。
为了更简单地实现适配器的功能,android提供了一个更为方便的类SimpleAdapter进行适配。SimpleAdapter的构造函数如下:
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String\[\] from, int \[\] to)
第一个参数是显示适配内容的activity的上下文,第二个参数看起来比较复杂,这是一个List类型的对象data,里面存放着一个继承自Map类型的任意对象,而这个Map中存放的第一个数据类型,即key值为String类型,第二个为任意类型,第三个参数是我们需要存放进去的item的layoutid,第四个参数为map中的key的集合,第五个参数为map中第二个参数在layout中对应的各自的id。
上述例子进行简单修改如下:
List<Map<String,Object>> list1 = new ArrayList<Map<String,Object>>();
for ( int i=0;i< mTitles . length ;i++) {
Map<String,Object> m= new HashMap<String,Object>();
m.put( "title" , mTitles \[i\]);
m.put( "icon" , R.drawable. icon );
list1.add(m);
}
SimpleAdapter adapter = new SimpleAdapter( this ,list1,R.layout. item , new String\[\]{ "icon" , "title" },
new int \[\]{R.id. malone ,R.id. malone1 });
list.setAdapter(adapter);
即可实现同样的效果。
接下来查看SimpleAdapter源码:
其内部定义了一个私有的对象:
privateList<? extendsMap<String, ?>> mData;
并在构造的时候将其初始化。
SimpleAdapter重写了getCount、getView方法,完成了之前相同的功能
事实上,SimpleAdapter进行了一次简单的适配,将需要适配的数据统一转化为存有map数据的list,给开发者提供了统一的、方便的接口,让开发者使用起来更为方便。