Android Studio 之 BaseAdapter 学习笔记
阅读原文时间:2023年07月09日阅读:1

•前行必备——ListView的显示与缓存机制

  我们知道 ListView、GridView 等控件可以展示大量的数据信息。

  假如下图中的 ListView 可以展示 100 条信息,但是屏幕的尺寸是有限的,只能显示下图中的 7 条。

  当向上滑动 ListView 的时候,item1 被滑出了屏幕区域,那么系统就会将item1回收到Recycler中,即View缓冲池中;

  而将要显示的 item8 则会从缓存池中取出布局文件,并重新设置好 item8 需要显示的数据,并放入需要显示的位置。

  这就是ListView的缓冲机制,总结起来就是一句话:需要时才显示,显示完就被会收到缓存。

  ListView,GridView 等数据显示控件通过这种缓存机制可以极大的节省系统资源。

  示意图如下:

    

•步入主提——BaseAdapter

  使用 BaseAdapter 比较简单,主要是通过继承此类来实现 BaseAdapter 的四个方法:

  • public int getCount() : 适配器中数据集的数据个数;

  • public Object getItem(int position) : 获取数据集中与索引对应的数据项;

  • public long getItemId(int position) : 获取指定行对应的ID;

  • public View getView(int position,View convertView,ViewGroup parent) : 获取每一行 Item 的显示内容。

      下面通过一个简单示例演示如何使用BaseAdapter。

准备工作

  新建一个项目,选择 Empty Activity 选项;

  Android Studio 会自动为我们生成两个文件 MainActivity.java 和 activity_main.xml;

activity_main.xml


<ListView  
    android:id="@+id/lv\_base\_adapter"  
    android:layout\_width="match\_parent"  
    android:layout\_height="match\_parent"  
    />

  在 res/layout 新建一个 base_adapter_item.xml 文件;

base_adapter_item.xml


<ImageView  
    android:id="@+id/header"  
    android:layout\_width="wrap\_content"  
    android:layout\_height="wrap\_content"  
    android:scaleType="centerCrop"  
    />  
<LinearLayout  
    android:layout\_width="match\_parent"  
    android:layout\_height="160dp"  
    android:orientation="vertical"  
    android:layout\_marginLeft="10dp">

    <TextView  
        android:id="@+id/name"  
        android:layout\_width="match\_parent"  
        android:layout\_height="wrap\_content"  
        android:textSize="20sp"  
        android:gravity="left"/>  
    <TextView  
        android:id="@+id/desc"  
        android:layout\_width="match\_parent"  
        android:layout\_height="wrap\_content"  
        android:textSize="13sp"  
        android:gravity="left"/>  
</LinearLayout>  

页面布局说明

  1. 整体采用线性布局,并以水平排列。

  2. 左边第一项定义一个ImageView组件,用来显示图片。

  3. 右边嵌套一个线性布局,并以垂直排列。

  4. 嵌套的线性布局里面定义了两个TextView,一个显示名字,一个显示介绍。

      base_adapter_item布局示意图如下:

        

      新建 Fruit 类;

Fruit.java

public class Fruit {
private String name;//名称
private String desc;//描述
private int imgId;//对应图片id

public Fruit(String name,String desc,int imgId){  
    this.name = name;  
    this.desc = desc;  
    this.imgId = imgId;  
}

public String getDesc() {  
    return desc;  
}

public String getName() {  
    return name;  
}

public int getImgId() {  
    return imgId;  
}  

}

  通过此 Fruit类,我们就将要显示的数据与 ListView 的布局内容(base_adapter_item)一一对应了;

  每个 Fruit 对象对应 ListView 的一条数据,这种方法在 ListView 中使用的非常广泛。

  接下来需要创建一个自定义的适配器,这个适配器继承自 BaseAdapter,并将 List 的泛型指定为 Fruit 类。

  新建 MyBaseAdapter 类;

MyBaseAdapter.java

public class MyBaseAdapter extends BaseAdapter {

private List<Fruit> list;//数据源  
private Context context;//调用 MyBaseAdapter 的上下文对象

//通过构造方法将数据源与数据适配器关联起来  
public MyBaseAdapter(Context context,List<Fruit>list){  
    this.context = context;  
    this.list = list;  
}  
@Override  
//ListView需要显示的数据数量  
public int getCount() {  
    return list.size();  
}

@Override  
//指定的索引对应的数据项  
public Object getItem(int position) {  
    return list.get(position);  
}

@Override  
public long getItemId(int position) {  
    return position;  
}

static class ViewHolder{  
    private ImageView imgView;  
    private TextView name;  
    private TextView desc;  
}  
@Override  
//返回每一项的显示内容  
public View getView(int position, View convertView, ViewGroup parent) {

    View view;  
    ViewHolder holder;

    if(convertView == null){  
        //由于我们只需要将 fruit.XML 转化为 View,并不涉及到具体的布局  
        // 所以第二个参数通常设置为null  
        view = LayoutInflater.from(context).inflate(R.layout.base\_adapter\_item, null);  
        holder = new ViewHolder();  
        holder.imgView = view.findViewById(R.id.header);  
        holder.name = view.findViewById(R.id.name);  
        holder.desc = view.findViewById(R.id.desc);

        view.setTag(holder);  
    }else{  
        view = convertView;  
        holder = (ViewHolder) view.getTag();  
    }

    //获取第 position 项的数据  
    Fruit fruit = list.get(position);  
    holder.imgView.setImageResource(fruit.getImgId());  
    holder.name.setText(fruit.getName());  
    holder.desc.setText(fruit.getDesc());

    return view;  
}  

}

MyBaseAdapter代码分析

  listView在开始绘制的时候,系统首先调用  getCount() 函数,根据他的返回值得到 ListView 的长度。

  然后根据这个长度,调用  getView()  逐一绘制每一行,如果你的  getCount() 返回值是 0 的话,列表将不显示,同样 return 1,就只显示一行。

  系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器),当手动完成适配时,必须手动映射数据,这需要重写  getView()  方法。

  系统在绘制列表的每一行的时候将调用此方法。

   getView() 有三个参数:

  • position表示将显示的是第几行

  • covertView是从布局文件中 inflate 来的布局

      我们用LayoutInflater的方法将定义好的  base_adapter_item 文件提取成 View 实例用来显示。

      通过 view.findViewById()  找到布局文件中的控件;

      实例化 Fruit 类,通过 set 方法将数据对应到各个组件上;

      至此一个自定义的 listView 就完成了,现在让我们回过头从新审视这个过程。

      系统要绘制 ListView,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?

      调用 getView() 函数,在这个函数里面首先获得一个View(实际上是一个ViewGroup),然后再实例并设置各个组件,显示之。

      好了,绘制完这一行了,那再绘制下一行,直到绘完为止。

       PS : 有关 ViewHolder 的使用,参考我的这篇博客——提升ListView的运行效率。

      接下来,也是最后一步,修改 MainActivity.java 中的代码;

MainActivity.java

public class TestBaseAdapterActivity extends AppCompatActivity {

private ListView mLv;  
private MyBaseAdapter adapter;  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity\_test\_base\_adapter);

    mLv = findViewById(R.id.lv\_base\_adapter);  
    adapter = new MyBaseAdapter(TestBaseAdapterActivity.this,getData());  
    mLv.setAdapter(adapter);  
}

private List<Fruit> getData(){  
    List<Fruit> list = new ArrayList<>();

    //String name,String desc,int imgId  
    list.add(new Fruit("apple","苹果",R.drawable.apple));  
    list.add(new Fruit("banana","香蕉", R.drawable.banana));  
    list.add(new Fruit("oranges","橘子",R.drawable.oranges));

    return list;  
}  

}

  apple,banana,oranges 是我从网上下载的图片,放置在了 drawable 文件夹下:

        

•运行结果