Android—ListView 的各种使用方法

一、 AdapterView 及其子类

  AdapterView 是一组重要的组件,它的主要作用是通过列表的形式显示数据。

  AdapterView 本身是一个抽象类,常用的 ListView(列表)、Spinner(下拉列表)、Gallery(缩略图),GridView(网格图)都是 AdapterView 的子类。子类的用法相似,知识在显示上略有不同。

  AdapterView 的子类继承关系

  

  下面是 AdapterView 的类定义(成员变量及方法省略)

/**
 * An AdapterView is a view whose children are determined by an {@link Adapter}. 
*  AdapterView是一个视图,其子视图由{@link Adapter}确定 * See {
@link ListView}, {@link GridView}, {@link Spinner} and {@link Gallery} for commonly used subclasses of AdapterView.
*  提示AdapterView 的常用子类
*/ public abstract class AdapterView<T extends Adapter> extends ViewGroup { }

  AdapterView 具有如下特征:

  1. AdapterView 继承了 ViewGroup ,说明它本质是容器。

  2. AdapterView 可以包含多个 ”列表项“ (即子视图),子视图由与之关联的 Adapter 确定,以合适的方式显示出来。

二、AdapterView 的子类 ListView

  以垂直列表的形式显示所有的列表项,并且能够根据数据的长度自适应显示。下面就是使用 AdapterView 实现的效果

  

   ListView 的使用大致上可以分为四个步骤:添加 ListView 组件、存储数据、设置列表项item的布局文件、加载数据/资源进行显示、添加监听。下面讲解几个 ListView 的使用方法:

  1. 当整个Activity中只有一个ListView组件时,可以使用ListActivity。

  ListActivity类继承于Activity类,默认绑定了一个ListView组件,并提供一些与ListView处理相关的操作。

  ListActivity类常用的方法为getListView(),该方法返回绑定的ListView组件。

  一旦在程序中获得了ListView之后,接下来就需要为ListView设置它要显示的列表项了。在这一点上,ListView显示出了AdapterView的特征:通过setAdapter(Adapter)方法为之提供Adapter,并由Adapter提供列表项即可。

  2. 最简单的方法——通过 android:entried 调用加载数组资源

  a. 在布局文件中添加 ListView 组件 ( ListView 的 id 号为  android:list 

  使用特定 id 号的好处:在 listView 中可以直接通过 getListView() 方法得到 ListView 实例

<ListView
        android:id="@+id/android:list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />  

  b. 在 values 文件夹中添加或设置 string.xml 文件用来存储数据(为了体现滚轮效果,可以适量增加 item 数目)

 <string-array name="fruit_array">
        <item>Apple</item>
        <item>Banana</item>
        <item>Orange</item>
        <item>Watermelon</item>
        <item>Pear</item>
        <item>Grape</item>
        <item>Pineapple</item>
        <item>Strawberry</item>
        <item>Cherry</item>
        <item>Mango</item>
    </string-array>

  c. 通过 android:entried 调用加载数组资源

   <ListView
        android:id="@+id/android:list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/fruit_array"  />

  由于这个方法只用于显示文本类型的数据,不经常使用,添加监听的部分在这里就不展开讲解了

  3. AdapterView + Adapter

  使用数组资源创建 ListView 是一种非常简单的方式,但是这种 ListView 能定制的内容很少。如果想丰富 ListView 的外观、内容,添加组件的行为,就需要把 ListView 当作 AdapterView 来使用,通过 Adapter 自定义每一个 列表项的 外观、内容、行为动作等。

  AdapterView + Adapter 的工作原理

  

   类似于 MVC 框架,数据源(Model)存放数据,利用控制器(Controller)将数据显示在视图(View)上。

  ListView 相当于 V(View),用于显示视图;Adapter 相当于 控制器(Controller)

  当需要数据时,ListView 会从 Adapter 中取出数据进行显示。

  3-1 基于ArrayAdapter 使用 ListView

  a. 添加 ListView 组件(同上)

  b. 存储数据。存储数据的方式可以多种多样的,可以将数据存储在 string.xml 文件中,可以将其存储为 string[] 数组形式,也可以将其存储为 List 列表形式。

  c. 设置列表项item的布局文件

  item 中包含多种组件,例如 ImageView,Button 等,我们可以用 ArrayList 进行加载,将数据资源存储在一个类中,接着创建一个类继承 ArrayAdapter ,在 getView() 方法中将数据资源与 item 布局中的组件联系在一起即可,这在《第一行代码》有非常详细的讲解,在这篇博客中,我使用 SimpleAdapter 进行加载,会在下面进行讲解。

  该 LIstView 只显示最简单的当行文本内容,因此选择一个android 自带的简单布局

android.R.layout.simple_list_item_1

 

  d. 利用 adapter 将数据显示在 ListView 上。也就是实例化 ArrayAdapter 类,在 ListView 中添加 Adapter 两个步骤。

  实例化 ArrayAdapter 类

  ArrayAdapter 有很多的构造方法,例如

    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) {
        this(context, resource, 0, new ArrayList<>());
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId) {
        this(context, resource, textViewResourceId, new ArrayList<>());
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
        this(context, resource, 0, Arrays.asList(objects));
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull T[] objects) {
        this(context, resource, textViewResourceId, Arrays.asList(objects));
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @NonNull List<T> objects) {
        this(context, resource, 0, objects);
    }

public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @IdRes int textViewResourceId, @NonNull List<T> objects) { this(context, resource, textViewResourceId, objects, false); }

 

  我们可以发现,上面五个构造方法都是调用最后一个构造方法,而最后构造方法调用了另一个方法,这个方法是私有的。我们来详细看看这个构造方法

    /**
     * Constructor
     * @param context 当前上下文。
     * @param resource 子项布局id:布局文件的资源ID,其中包含在实例化视图时要使用的布局。
     * @param textViewResourceId 布局资源中要填充的TextView的ID
     * @param objects  要在ListView中表示的对象
     */
private ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mResource = mDropDownResource = resource;
        mObjects = objects;
        mObjectsFromResources = objsFromResources;
        mFieldId = textViewResourceId;
    }

  在上面的构造方法中,我们最经常使用的是第三个

    /**
     * Constructor
     * @param context 当前上下文。
     * @param resource 子项布局id:布局文件的资源ID,其中包含在实例化视图时要使用的布局。
     * @param objects  要在ListView中表示的对象
     */
public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
  
this(context, resource, 0, Arrays.asList(objects));
}

 

  可以看到,在这个方法中,使用 泛型 来表示传进来的要在 ListView 中显示的对象,着说明你可以传入许多种数据类型,但是最终会会使用 Arrays.asList(Objects)) 进行类型转换,返回 MutableList

  

   d. 添加监听——这里只添加 列表项的点击事件。使用

  总的代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//获取 ListView 控件 ListView listView = (ListView)findViewById(R.id.list_view);
//获取数据 final String[] fruitArray = getResources().getStringArray(R.array.fruit_array);
// 为 ListView 添加控制器 ArrayAdapter ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1,fruitArray); listView.setAdapter(adapter);
//为 ListView 的列表项添加鼠标点击事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { /** * @param adapterView 发生单击事件的列表项 ListView * @param view 被单击控件 view * @param i 在列表项中的位置 position * @param l 被单击列表项的行ID */ @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { String Tag = "onItemClick======"; Log.d(Tag,"position="+i); Log.d(Tag,"行 ID"+l); Toast.makeText(MainActivity.this,fruitArray[i],Toast.LENGTH_SHORT); } }); } }

  3-2 基于 SimpleAdapter 使用 ListView

  SimpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片),还可以放上Button(按钮),CheckBox(复选框)等等

  a. 添加 ListView 组件(同上)

  b. 设置列表项 item 的布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/image"
        android:layout_weight="4"
        android:layout_width="0dp"
        android:layout_height="150dp"
        android:layout_gravity="left"
        android:layout_marginLeft="1dp"
        android:padding="7dp"/>
    <LinearLayout
        android:layout_weight="6"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center"
        android:layout_marginLeft="5dp">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"/>
        <TextView
            android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"/>
    <Button
        android:id="@+id/button"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@drawable/btn"
        android:layout_gravity="right"
        android:layout_marginRight="30dp"
        android:padding="10dp"
        android:focusable="false"/>
        <!--android:focusable="false"-->
    </LinearLayout>
</LinearLayout>

 

  效果:

  c. 存储数据。
  同样,我们通过分析 SimplaeAdapter 构造方法来决定数据类型。SimpleAdapter 只有一个构造方法

 /**
     * Constructor
     *
     * @param context 运行与此SimpleAdapter关联的View的上下文,即放置 ListView 的上下文环境
     * @param data 数据为一个列表 list ,list 中的数据以 Map 类型存储列表中的每个条目对应于列表中的一行。 列表中的每个条目对应于列表中的一行。 
     * @param resource 列表项的布局文件
     * @param from A list of column names that will be added to the Map associated with each item.
     * @param to The views that should display column in the "from" parameter. These should all be
     *        TextViews. The first N views in this list are given the values of the first N columns
     *        in the from parameter.
     */
    public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

 

  可以知道,数据存储在 List 中,List 中每一项数据对应 ListView 的一行,以 Map 的形式进行存储。 from 和 to 数组分别代表Map中 key 值和 Item 布局文件中组件的 ID 号,呈一一对应关系。

 public List<HashMap<String,Object>> getData(){
        List<HashMap<String,Object>> list = new ArrayList<>();

        HashMap<String,Object> map;
        map= new HashMap<>();
        map.put("image",R.drawable.i1);
        map.put("title","开心的哆啦A梦");
        map.put("info","cute cute cute cute cute cute cute cute cute cute ");
        list.add(map);

        map = new HashMap<>();
        map.put("image",R.drawable.i2);
        map.put("title","贪吃的哆啦A梦");
        map.put("info","卡哇伊  卡哇伊 卡哇伊 卡哇伊 卡哇伊 卡哇伊 卡哇伊 ");
        list.add(map);

        map = new HashMap<>();
        map.put("image",R.drawable.i3);
        map.put("title","大哭的哆啦A梦");
        map.put("info","cute cute cute cute cute cute cute cute cute cute ");
        list.add(map);
        return list;

    }

  d. 利用 adapter 加载数据/资源进行显示

//初始化列表数据
final List<HashMap<String,Object>> list = getData();
//为ListView 添加 Adapter
String[] mapKeyArray = {"image","title","info"};
 int[] layoutIdArray = {R.id.image,R.id.title,R.id.info};
SimpleAdapter adapter = new SimpleAdapter(MainActivity.this,list,R.layout.listview_item, mapKeyArray , layoutIdArray );

  e. 添加监听

  如果没有重写 Adapter 的 getView() 方法,给按钮单独添加监听是比较麻烦的,所以我们可以使用 BaseAdapter 再给按钮添加监听,这里给 Item 列表项添加监听。添加的方法和使用 ArrayAdapter 一样。所以这里就不展开了。

  但是需要注意的是,由于 列表项中添加的 Button 按钮,按钮组件会获取焦点,所以需要设置 button 的 focusable 为 false。可以直接设置 button 的属性,也可以用java 方法进行设置。例如上面共参考的 item 布局文件就已经设置了。

   

   3-3 基于 BaseAdapter 使用 ListView

  添加 ListView 组件,存放数据,设置列表项的布局文件都和 SimpleAdapter 中的操作相同

  d. 创建一个 Adapter 继承 BaseAdapter,并实现抽象方法。

   BaseAdapter 有 4 个抽象方法

 int getCount();    //返回的是数据源对象的个数,即列表项数
 Object getItem(int var1);    //返回指定位置position上的列表项
 long getItemId(int var1);    //返回指定位置处的行ID
View getView(int var1, View var2, ViewGroup var3);    //返回列表项对应的视图

 

  继承 BaseAdapter 时需要去实现这 4 个抽象方法,这几个抽象方法都是 Adapter 接口中定义的方法。

  以理解为adapter先由getCount确定数量,然后循环执行getView()方法将条目一个一个绘制出来。

  必须重写的方法是getCount和getView方法。

  前三个方法基本不需要过多修改,

    public int getCount() {
        return mData.size();
    }
   public Object getItem(int position) {
        return mData.get(position);
    }
    public long getItemId(int position) {
        return position;
    }

 

   由于新建了一个 java 文件,所以建议把 上下文 context,和 存储数据的列表 list 传过来

public class BabyAdapter extends BaseAdapter {

    Context context;
    List<HashMap<String,Object>> list ;
    public BabyAdapter(Context context,List list){
        super();
        this.context = context;
        this.list = list;
    }

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

    @Override
    public Object getItem(int i) {
        return list.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }
    @Override
    public View getView(final int i, View convertView, ViewGroup viewGroup) {
   /** 在不重写getView 方法的情况下,每次调用 getView 时都要重新实例化子项 item 的布局文件,然后通过 findViewById 重新寻找 View 组件并绘画
       着会导致两个问题,1. 重复加载布局文件;2. 重复多次寻找 View 组件
       解决:使用 convertView 作为 View 缓存,形成 adapter 的itemView 重用机制,减少重绘 view 的次数
       形参 view 中缓存了itemView 的布局文件
  */
View view; ViewHolder holder; if(convertView == null){
      // LayoutInflater 用于加载布局的系统服务,实例化与Layout XML文件对应的View对象
      // 不
能直接使用, 需要通过getLayoutInflater( )方法或getSystemService( )方法来获得与当前Context绑定的 LayoutInflater实例。
          LayoutInflater factory = LayoutInflater.from(context);
          // reSource:View 的layout 的ID
          // root: 生成的view 对象的父控件。若提供了 root(!null),则返回 root 作为根结点,否则,返回 view 对象的根布局作为根布局,
view
= factory.inflate(R.layout.listview_item,null); // 2. 使用 ViewHolder 实现 View 组件的缓存 // 重用 View 时就不用通过 findViewById 重新寻找 view 组件,同时减少 view 组件重绘的次数 holder = new ViewHolder(); holder.image =(ImageView)view.findViewById(R.id.image); holder.title = (TextView)view.findViewById(R.id.title); holder.info = (TextView)view.findViewById(R.id.info); holder.button = (Button)view.findViewById(R.id.button); view.setTag(holder); }else{ view = convertView; holder = (ViewHolder)view.getTag(); } // 2. 使用 ViewHolder 实现 View 组件的缓存 // 重用 View 时就不用通过 findViewById 重新寻找 view 组件,同时减少 view 组件重绘的次数 HashMap<String,Object> map = list.get(i); holder.image.setImageResource((int)map.get("image")); holder.title.setText((String)map.get("title")); holder.info.setText((String)map.get("info")); // 给按钮添加监听 holder.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new AlertDialog.Builder(context) .setTitle(list.get(i).get("title").toString()) .setMessage(list.get(i).get("info").toString()) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }).show(); } }); return view; } class ViewHolder{ ImageView image; TextView title ; TextView info; Button button; } }

 

   其中,converView 指 列表项视图,当该 item 从未再屏幕上出现过时,convertView 为空,一旦出现过,convertView 中就缓存了View 对象,下一次就可以直接取得 列表项中视图对象使用而不用再加载。

 三、ListView 的子项 Item 缓存原理

  1. 假设:屏幕只能显示5个Item,那么ListView只会创建(5+1)个Item的视图;当第1个Item完全离开屏幕后才会回收至缓存从而复用(用于显示第6个Item)

   

 

   2. 假设:屏幕只能显示5个Item,那么ListView只会创建(5+1)个Item的视图;当第1个Item完全离开屏幕后才会回收至缓存从而复用(用于显示第7个Item)

   

posted @ 2020-04-13 01:03  葡萄籽pp  阅读(4097)  评论(0编辑  收藏  举报