【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter

时间:2022-06-02 20:38:13

目录(?)[-]

  1. 设计Adapter的布局
  2. 代码部分
    1. Activity的代码
    2. MyAdapter的代码数据源和构造函数
    3. MyAdapter的代码实现自定义的adapter
    4. MyAdapter的代码继续探讨BaseAdapter

我们可以同继承抽象类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-GalleryViewAndroid学习笔记(十九):建立自己的ListView 。

相关链接: 我的Android开发相关文章