我们可以同继承抽象类BaseAdapter来实现自己的Adapter,自己设置子View的UI,不同子View可以由不同的布局,并自己进行数据和子view中数据的对应关系。图是例子的呈现结果,我们有很多图标,对这些图标按一定大小进行缩放,然后布局在GridView中。这个例子很简单。
设计Adapter的布局
Activity的layout很简单,就只有一个GridView,我们继续沿用之前GridView例子中的XML文件,见Pro Android学习笔记(二十):用户界面和控制(8):GridView和Spinner,不再重复。
我们设计每个网格,即子view的布局,ui_gridimage.xml如下,也很简单,只含有一个控件ImageView,id为R.id.ui_myimage。
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ui_myimage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#555"
android:scaleType="centerInside"
android:padding="5dip"
android:maxHeight="50dip"
android:maxWidth="50dip" />
代码部分
Activity的代码
由于这个例子很小,自定义的Adapter以内部类的方式放在Activity中,一般应当独立为一个class。Activity的代码如下:
public class UiGridCustomTest1 extends Activity{
/* 和系统提供的Adapter方式在调用上没有什么不同,只是我们将在MyAdapter中直接获取数据,和直接指定如何呈现,不需要传递数据源等信息。*/
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ui_gridview);
GridView gv = (GridView)findViewById(R.id.ui_grid);
MyAdapter adapter = new MyAdapter(this);
gv.setAdapter(adapter);
}
/* MyAdatpter是自定义的Adapter。本例以内部类方式出现 */
private class MyAdapter extends BaseAdapter{
......
}
}
接下来,我们重点看看MyAdapter的代码。
MyAdapter的代码:数据源和构造函数
private class MyAdapter extends BaseAdapter{
private static final String TAG="MyAdapter"; //用于Log.d(TAG, ……);
private Context mContext = null;
private LayoutInflater infalter = null;
/* 【数据源(1)】: icons[]是图片资源的ID,是原始数据,通过资源ID生产Bitmap图片 myImages[],这是原始尺寸的图片,实际上,我们对图片做了进行伸缩处理,放置在myThumbs[]中,而myThumbs是需要呈现在UI的图片。 */
private int[] icons = {R.drawable.png01,R.drawable.png02,…(省略)…,R.drawable.png20};
private Bitmap[] myImages = new Bitmap[icons.length]; //原图
private Bitmap[] myThumbs = new Bitmap[icons.length]; //按尺寸伸缩图
private int convertViewCount = 0; //用于Log.d中的跟踪信息。
/* 用于存储子view中的控件,本例只有一个,比较简单。 */
private class ViewHolder{
ImageView image;
}
public MyAdapter(Context context){
/*【初始化(1)】:保存context,创建infalter,并于context关联。infalter可 从xml中创建view对象,非常方便。*/
mContext = context;
infalter = LayoutInflater.from(mContext);
/*【初始化(2)】【数据源(2)】:创建数据源,将资源ID转换为最终呈现的Bitmap信息*/
for(int i = 0 ; i <icons.length; i ++){
myImages[i] = BitmapFactory.decodeResource(context.getResources(), icons[i]);
//还可以使用ThumbnailUtils来处理相关的图片和视频
myThumbs[i] = Bitmap.createScaledBitmap(myImages[i], 100, 100, false);
}
}
......
}
MyAdapter的代码:实现自定义的adapter
MyAdapter集成抽象类BaseAdapter,有4个抽象方法必须实现,实际是来自BaseAdatper实现的Adapter接口。
private class MyAdapter extends BaseAdapter{
... ...
//How many items are in the data set represented by this Adapter.
//需要显示多少个item,本例即图片的数目。
public int getCount() {
Log.d(TAG,"getCount() return " + icons.length);
return icons.length;
}
// Get the data item associated with the specified position in the data set.
// 根据position返回该子view对应的data,用户无需考虑具体的UI布局就可获取data,拘役data如何获取和adapter的具体实现相关,例如CursorAdapter中读取联系人姓名的例子,我们是通过_ID从content provider中读取,在此可以直接返回null。
public Object getItem(int position) {
Log.d(TAG,"getItem(" + position + ")" );
return icons[position];
}
// Get the row id associated with the specified position in the list.
// 根据position获得rowId,在CursorAdapter的例子中,rowId为_ID,不一定和position一致,又例如在ListView中一行数据一行分隔符(分隔符也是一个子view,占一个position)的显示方法, rowId和position也不一致。
public long getItemId(int position) {
Log.d(TAG,"getItemId(position) is " + position);
return position;
}
// Get a View that displays the data at the specified position in the data set.
// 这是子View布局显示的实现,是最关键的代码部分。Android只会询问当前显示的子View的呈现, 也就是不显示的子view,系统不会调用getView(),这就很好地提高了性能。系统要显示某个子view的使用,就会根据position来调用该方法。
//参数2:convertView就是子View,如果第一次要求给出position的子view的呈现,convertView为null,我们上下滚动屏幕,系统会要求获得显示部分的各个子view的具体呈现,如果该子view之前已经创建,则convertView就是之前子view对象,我们可以利用系统保存的之前已经实现的子view对象,简化代码,提升效率。参数3 parent,从本例的层次上看就是GridView
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder; //存放该子view的控件,这样我们不用每次都通过Id来查找,直接引用对象。
Log.d(TAG,"getView() : position = " + position
+ " convertView = " + (convertView == null ? "null" : convertView.toString())
/*+ " parent : " + (parent == null ? "null" : parent.toString())*/);
//实现布局
if(convertView == null){ //子view第一次出现,需要构造,将重要内容(本例为控件对象)放置在viewHolder,并通过setTag()存放。
convertView = infalter.inflate(R.layout.ui_gridimage, null ); //通过xml来创建view
convertViewCount ++;
Log.d(TAG,"convertViewCount is " + convertViewCount);
holder = new ViewHolder();
holder.image = (ImageView) convertView.findViewById(R.id.ui_myimage);
// holder.image.setImageBitmap(myThumbs[position]);
convertView.setTag(holder);
}else{ //子view已经出现过,利用原来已经创建的对象,获得控件信息
holder = (ViewHolder)convertView.getTag();
Log.d(TAG,"image is " + holder.image.toString());
}
//将图片在view中呈现,这里有个地方不是很明白,我们如果放在convertView == null中,即只在第一次出现的时候对设置控件的图片,如果我们上下滚动屏幕,发现图片显示出现混乱现象,为何?
holder.image.setImageBitmap(myThumbs[position]);
return convertView; //返回子view的对象
}
}
MyAdapter的代码:继续探讨BaseAdapter
实现上面四个抽象函数,以及可以完成我们的例子,我们继续对BaseAdapter的其他一些有趣函数进行分析,我们还可以在例子中增加一些重写的方法。
private class MyAdapter extends BaseAdapter{
... ...
//返回在AdapterView中需要显示多少种子view,本例只有ImageView一种,故返回1。例如ListView,如果在数据之间有separator,即一行textView,一行separator,则返回2。
public int getViewTypeCount() {
Log.d(TAG, "in getViewTypeCount() :only one Type");
return 1;
}
//在创建每个子view,也就是在getView()之前,都调用该函数,询问这是哪种子view,如果有2种子view的,在返回值为0或1,如果有3种,返回0,1,2。本例只有一种子view,所以无论是哪个position,都返回0。在使用separator的例子中中,根据奇偶返回0,1,此外还要使用isEanble(int position),对于separator要返回false。
public int getItemViewType(int position) {
Log.d(TAG, "in getItemViewType() for position " + position);
return 0;
}
}
有更多的自定义Adapter的例子,可以参考Android学习笔记(十五):Activity-GalleryView、Android学习笔记(十九):建立自己的ListView 。
相关链接: 我的Android开发相关文章