ListView控件功能全解析

来源:互联网 发布:软件性能指标描述 编辑:程序博客网 时间:2024/06/15 13:34

ListView是Android手机系统中使用非常频繁的控件,以垂直列表的形式显示数据项,ListView的用法也较为复杂,下面将详细介绍ListView相关的各种用法。

1、创建使用ListView

有2种方式创建ListView

  • 在布局文件中使用ListView创建
<ListView         android:id="@+id/list"        android:layout_width="match_parent"        android:layout_height="match_parent"/>

在代码中获取到实例后为ListView设置Adapter

        listView = (ListView)findViewById(R.id.list);        listView.setAdapter(adapter);
  • Activity继承ListActivity

继承ListActivity后,不需要设置布局文件,直接使用setListAdapter()为ListView设置Adapter即可

    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setListAdapter(adapter);    }

2、自定义ListView的item格式

新建布局文件item.xml,代码如下:

<TextView         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:gravity="center"        android:textColor="#F00"        android:textSize="16sp"/>

将文字设为居中显示,设置了文本的颜色和大小,构造Adapter时将布局文件传入

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.item, datas);        listView.setAdapter(adapter);

这里写图片描述

3、设置ListView的点击事件

为ListView的item设置点击事件

listView.setOnItemClickListener(new OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> parent, View view,                    int position, long id) {                // TODO Auto-generated method stub                Toast.makeText(MainActivity.this, "Item " + datas.get(position) + " clicked", Toast.LENGTH_LONG).show();            }        });

运行程序,点击列表项

这里写图片描述

4、ListView数据改变,更新ListView

修改ListView的点击事件,点击item后删除List中的数据项,数据改变后,页面不会自动刷新,需要调用adapter的notifyDataSetChanged()方法通知列表更新

listView.setOnItemClickListener(new OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> parent, View view,                    int position, long id) {                // TODO Auto-generated method stub                Toast.makeText(MainActivity.this, "Item " + datas.get(position) + " deleted", Toast.LENGTH_LONG).show();                datas.remove(position);                adapter.notifyDataSetChanged();            }        });

运行程序,点击任意列表项

这里写图片描述

5、ListView显示多种类型的item

上面的例子都是显示的一个类型的列表项,只包含一个TextView,有时会需要显示几种不同的item,包含图片、文本、按钮等元素,这就需要为ListView定义几个不同的布局文件,一个布局文件代表一种类型的item

布局文件item1.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <ImageView         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentLeft="true"        android:layout_centerVertical="true"        android:src="@drawable/ic_launcher"/>    <TextView         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:textSize="20sp"/></RelativeLayout>

布局文件item2

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <TextView         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:textSize="20sp"/></RelativeLayout>

布局文件item3

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <TextView         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:textSize="20sp"/>    <CheckBox         android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"/></RelativeLayout>

布局文件1中定义了一个ImageView和一个TextView,布局文件2中只有一个TextView,布局文件3中定义了一个TextView和一个CheckBox,下面是MainActivity.java文件

public class MainActivity extends Activity {    ListView listView;    List<String> datas = new ArrayList<String>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listView = (ListView)findViewById(R.id.list);        initData();        adapter = new ArrayAdapter<String>(this, R.layout.item, datas);        listView.setAdapter(new ListAdapter());    }    class ListAdapter extends BaseAdapter {        public final int TYPE_1 = 0;        public final int TYPE_2 = 1;        public final int TYPE_3 = 2;        @Override        public int getCount() {            // TODO Auto-generated method stub            return datas.size();        }        @Override        public long getItemId(int position) {            // TODO Auto-generated method stub            return position;        }        @Override        public Object getItem(int position) {            // TODO Auto-generated met hod stub            return datas.get(position);        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            // TODO Auto-generated method stub            int viewType = getItemViewType(position);            View view = null;            TextView text = null;            switch(viewType) {                case TYPE_1:                    view = View.inflate(MainActivity.this, R.layout.item1, null);                    text = (TextView)view.findViewById(R.id.text1);                    break;                case TYPE_2:                    view = View.inflate(MainActivity.this, R.layout.item2, null);                    text = (TextView)view.findViewById(R.id.text2);                    break;                case TYPE_3:                    view = View.inflate(MainActivity.this, R.layout.item3, null);                    text = (TextView)view.findViewById(R.id.text3);                    break;            }            text.setText(datas.get(position) + "");            return view;        }        @Override        public int getViewTypeCount() {            // TODO Auto-generated method stub            return 3;        }        @Override        public int getItemViewType(int position) {            // TODO Auto-generated method stub            if(position < 5) {                return TYPE_1;            } else if(position < 10) {                return TYPE_2;            } else {                return TYPE_3;            }        }    }    public void initData() {        for(int i = 1; i <= 10; i++) {            datas.add(i + "");        }    }}

重写BaseAdapter的getViewTypeCount()和getItemViewType()方法,定义了3种不同的布局,getViewTypeCount中返回item类型的数目,在getItemViewType中根据位置返回布局类型,getView中根据不同的type加载不同的布局文件,最后,将Adapter设置到ListView中

运行效果:

这里写图片描述

6、ListView显示优化

ListView在使用过程经常会遇到滑动卡顿的性能问题,所以如何优化ListView的加载就显得尤为重要,ListView的性能优化主要包括几个方面

  • 1、利用convertView复用之前的缓存布局,减少加载布局的次数

ListView在滚动的过程中,每加载一个View都会重新加载一遍布局文件,如果ListView中的条目非常多,在快速滑动时,就会导致列表卡顿。在getView()方法中有一个convertView参数,向上滑动列表时,隐藏的view会缓存在这个convertView中,新显示的view可以复用隐藏的convertView,这样就不会每次都加载一遍布局

示例代码

@Override        public View getView(int position, View convertView, ViewGroup parent) {            // TODO Auto-generated method stub            if(convertView == null) {                convertView = View.inflate(MainActivity.this, R.layout.item, null);            }            TextView text = (TextView)convertView.findViewById(R.id.text1);            text.setText(datas.get(position));            return convertView;        }

在加载布局时,判断convertView是否为空,为空时再去加载布局文件,这样能最大的减少加载布局的次数,提升效率

  • 2、使用自定义ViewHolder类,减少findViewById的次数

在解析布局文件查找控件时,需要不断的解析每个节点,如果可以减少查找控件的时间,会对ListView的加载性能提升不少。

示例代码

@Override        public View getView(int position, View convertView, ViewGroup parent) {            // TODO Auto-generated method stub            ViewHolder viewHolder;            if(convertView == null) {                viewHolder = new ViewHolder();                convertView = View.inflate(MainActivity.this, R.layout.item, null);                viewHolder.textView = (TextView)convertView.findViewById(R.id.text);                convertView.setTag(viewHolder);            } else {                viewHolder = (ViewHolder)convertView.getTag();            }            viewHolder.textView.setText(datas.get(position));            return convertView;        }//自定义ViewHolder类class ViewHolder {            TextView textView;  //定义item中显示的所有控件        }

在第一次创建View的时候,查找到item中的控件,并存储到ViewHolder类中,通过setTag()方式将ViewHolder设置到convertView中,下次再加载View的时候直接从convertView中获取

  • 3、利用ListView的分页加载功能,一次只加载一页条目

如果ListView的条目相当多,在显示ListView的时候一次把所有的条目全部加载完,势必会很耗时间,也会导致列表相响应慢,这时可以考虑来分批加载数据

下面利用ListView的OnScrollListener接口实现ListView的滑动加载功能

主布局文件只有一个ListView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.example.testlistview.MainActivity" >    <ListView         android:id="@+id/list"        android:layout_width="match_parent"        android:layout_height="match_parent"/></RelativeLayout>

在列表底部有一个Button,点击Button可以加载更多条目,向上滑动到底部也会自动加载更多条目,加载时Button隐藏,需要显示一个加载的进度条

底部布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <Button         android:id="@+id/load"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:text="加载更多"/>    <LinearLayout         android:id="@+id/load_progress"        android:layout_width="wrap_content"        android:layout_height="20dp"        android:orientation="horizontal"        android:layout_gravity="center"        android:visibility="gone">        <ProgressBar             android:layout_width="wrap_content"            android:layout_height="wrap_content"/>        <TextView             android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="正在加载..."/>    </LinearLayout></LinearLayout>

ListView的滑动加载功能需要实现OnScrollListener接口,滑动加载只关注静止时的状态,当ListView滑动到最底部并且可见的条目数等于adapter的数目,自动加载。当加载的条目和最大条目数相等时,移除底部的View,同时在提示用户没有更多数据可以加载了

MainActivity.java

public class MainActivity extends ActionBarActivity {    ListView listView;    View footerView;    Button load;    View loadProgress;    List<String> datas = new ArrayList<String>();    ArrayAdapter<String> adapter;    public final int MAX_NUM = 50; //最大的条目数    int lastVisibleItem; //最后一个可见的item    Handler handler = new Handler();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listView = (ListView)findViewById(R.id.list);        footerView = getLayoutInflater().inflate(R.layout.load_more, null);        load = (Button)footerView.findViewById(R.id.load);        loadProgress = footerView.findViewById(R.id.load_progress);        initData();        listView.addFooterView(footerView); //将底部布局添加到ListView中        adapter = new ArrayAdapter<String>(this, R.layout.item, datas);        listView.setAdapter(adapter);        listView.setOnScrollListener(new OnScrollListener() {            @Override            public void onScroll(AbsListView view, int firstVisibleItem,                    int visibleItemCount, int totalItemCount) {                // TODO Auto-generated method stub                lastVisibleItem = firstVisibleItem + visibleItemCount -1;                //数据加载完毕,移除底部View                if(totalItemCount == MAX_NUM + 1) {                    listView.removeFooterView(footerView);                    Toast.makeText(MainActivity.this, "没有更多数据可加载", Toast.LENGTH_LONG).show();                }            }            @Override            public void onScrollStateChanged(AbsListView view, int scrollState) {                // TODO Auto-generated method stub                //当滑动状态为静止并且可见条目数目等于adapter的数目时,加载条目                if(scrollState == OnScrollListener.SCROLL_STATE_IDLE && lastVisibleItem == adapter.getCount()) {                    load.setVisibility(View.GONE);                    loadProgress.setVisibility(View.VISIBLE);                    handler.post(new Runnable() {                        public void run() {                            loadData();                            adapter.notifyDataSetChanged();                            load.setVisibility(View.VISIBLE);                            loadProgress.setVisibility(View.GONE);                        };                    });                }            }        });        load.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                // TODO Auto-generated method stub                //加载更多数据            }        });    }    public void loadData() {        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        int count = adapter.getCount();        for(int i = count + 1; i < count + 6; i++) {            datas.add(i + "");        }    }    public void initData() {        for(int i = 1; i <= 15; i++) {            datas.add(i + "");        }    }}

在代码中为listView添加了滚动监听,主要是实现OnScrollListener接口中的两个方法:onScroll()和onScrollStateChange(),在onScroll方法中计算最后一个可见条目的索引,在onScrollStateChanged中判断滑动状态,如果是静止状态,并且并且最后一个可见条目的索引等于adapter中的条目数,这时开始加载数据。

运行程序

这里写图片描述

ListView初始化时显示15条数据,向上滑动ListView

这里写图片描述

在程序中,启动一个线程,线程中等待3秒模拟耗时加载数据,每次加载5条数据,50条数据加载完毕后,底部View被移除,再继续向上滑动时,会给出提示,没有更多数据可以加载

这里写图片描述

7、ListView的选中模式

ListView具有4种选中模式:

  • ListView.CHOICE_MODE_NONE:默认模式
  • ListView.CHOICE_MODE_SINGLE:单选模式
  • ListView.CHOICE_MODE_MULTIPLE:多选模式,无排除性,会响应onClick事件
  • ListView.CHOICE_MODE_MULTIPLE_MODAL:多选模式,有排除性,不响应onClick事件

ListView的默认模式为不会选中任何一个列表项,单选模式只能选中一个列表项,多选模式可以选中多个列表项,ListView默认不会选中任何项

选中的列表项被存在一个SparseBooleanArray中,存储的方式为键值对存储,键存储的是item的索引,值为选中的状态,true或者false,默认模式下SparseBooleanArray为空,单选模式下存的是选中的item项,多选模式下存储所有选中过的项及选中状态

设置ListView为单选模式,在ListView的OnItemClick事件中获取列表项的选择状态

listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);listView.setOnItemClickListener(new OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> parent, View view,                    int position, long id) {                // TODO Auto-generated method stub                SparseBooleanArray array = listView.getCheckedItemPositions();                for(int i = 0; i < array.size(); i++) {                    Log.i(TAG, "选中项:" + array.keyAt(i) + ",选中状态:" + array.get(array.keyAt(i)));                }            }        });

点击多个列表项

这里写图片描述

我按顺序点击了1到4的列表项,从打印的log看出SparseBooleanArray中只保存了一个选中的item状态

修改ListView的选中模式为多选模式

listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

运行程序,依次点击1-3的列表项

这里写图片描述

从log中看出,点击第一个列表项时,SparseBooleanArray中只保存了一条数据,点击多个列表项,会在SparseBooleanArray中保存所有点击过的item,再次点击1-3的列表项

这里写图片描述

再次点击选中的item项,SparseBooleanArray中依然保存点击过的列表项,只是把选中状态置为了false

根据ListView多选状态的特性,可以在开发过程中遇到需要突出显示选中列表项的时候,利用item的选中状态来改变选中item的颜色,下篇博客中包含这个例子和ListView的另一个多选状态

ListView的两种多选模式:http://blog.csdn.net/zh175578809/article/details/72802461

原创粉丝点击