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
        android:id="@+id/list_view"
        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/list_view"
        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 A List of Maps. 
               数据为一个列表 list ,list 中的数据以 Map 类型存储
             Each entry in the List corresponds to one row in the list.
               列表中的每个条目对应于列表中的一行。 
            The Maps contain the data for each row, and should include all the entries specified in "from"
               列表中的每个条目对应于列表中的一行。  *
@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 组件

        //1. 使用 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)

   

猜你喜欢

转载自www.cnblogs.com/lyw-hunnu/p/12687201.html