学习笔记——ListView的使用

作为一个临近毕业的应届生,这是我第一次实习做android的相关开发,看书时候觉得自己学的差不多了,真正到做项目的时候,才发现,其实自己只是懂一些函数的用法,真正的实现细节根本不是很明白,特开始写下博客,一来记录自己的成长,二来回顾自己每周的收获,以加深印象,三来也是为了记录一些自己有用的资料,以便以后自己可以使用。现在说自己的使用心得,为时尚早,只能说收集更多资料,集百家之长。

这周接触最多的就是ListView这个控件,在安卓开发中,这也是个用的特别多,也是特别实用的控件。说一说关于ListView的工作原理。

ListView的工作原理如下:


ListView针对每个Item,要求adapter返回一个视图(getView()),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView(),一行一行的绘制ListView的每一项,如果getCount返回值是0的话,列表一行都不会显示,返回几,则显示几行。当有几千几万甚至更多的Item时,不可能为每个Item创建一个新的View,实际上,Android早已缓存了这些视图,下面这个图是解释ListView工作原理最经典的图。(Android中有个叫做Recycler的构件,与Recycler相关的已经由Google做过N多优化的东西有:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等待)


下面简单说下上图的原理:

1.如果你有几千几万甚至更多的选项(Item)时,其中只有可见的项目存在内存(一般说的的ListView优化,就是指在内存中的优化)中,其他的在Recycler中

2.ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的

3.当item1滚出屏幕时,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图,convertView此时不是空值了,它的值是item1。只需设定新的数据然后返回convertView,不必重新创建一个视图。

下面一个是示例的代码,以便加深理解:


public class MultipleItemsList extends ListActivity {
 
    private MyCustomAdapter mAdapter;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new MyCustomAdapter();
        for (int i = 0; i < 50; i++) {
            mAdapter.addItem("item " + i);
        }
        setListAdapter(mAdapter);
    }
 
    private class MyCustomAdapter extends BaseAdapter {
 
        private ArrayList mData = new ArrayList();
        private LayoutInflater mInflater;
 
        public MyCustomAdapter() {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }
 
        public void addItem(final String item) {
            mData.add(item);
            notifyDataSetChanged();
        }
 
        @Override
        public int getCount() {
            return mData.size();
        }
 
        @Override
        public String getItem(int position) {
            return mData.get(position);
        }
 
        @Override
        public long getItemId(int position) {
            return position;
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            System.out.println("getView " + position + " " + convertView);
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item1, null);
                holder = new ViewHolder();
                holder.textView = (TextView)convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.textView.setText(mData.get(position));
            return convertView;
        }
 
    }
 
    public static class ViewHolder {
        public TextView textView;
    }
}
 执行程序,查看日志:



图上可见项目为13个,然后查看其对应的Logcat


getView一共被调用了13次,并且convertView对于所有的可见项目为null。


向下滚动直到item14出现,convertView仍为null。因为recycler中没有视图,其中item0的边缘任然可见,在顶端,继续滚动:


item15出现,item0消失在了顶端,此时的convertview也不为空了,item0消失去了recycler中去了,然后item15被创建


所以一般在getView方法中会看见这样一个判断:

if(convertView==null){

.......

}else{

.....

}


以上就是listview的原理。。。下面再来总结一下listview的绘制过程

ListView的绘制过程:



首先,系统在绘制ListView之前,将会先调用适配器中的getCount方法来获取item的个数,之后每绘制一个item就会调用一次getView方法,在此方法内就可以引用实现定义好的xml来确定显示的效果并返回一个View对象作为一个item显示出来,也正是在这个过程中完成了适配器的主要转换功能,把数据和资源以开发者想要的效果显示出来,也正是getView的重复调用,是得ListView的使用更加简单和灵活。这两个方法是自定ListView显示效果中最为重要的,同时只要重写好了这两个方法,ListView就能完全按开发者的要求显示,儿getItem和getItemId方法将会在调用ListView的响应方法的时候被调用到,所以要保证ListView的各个方法有效的话,这两个方法也得重写,比如:没有完成getItemId方法功能的实现的话,当调用ListView的getItemIdPosition方法时,将不会得到想要的结果,因为该方法就是调用了对应的适配器的getItemId方法。


接下来说一下如何创建一个ListView:


列表的显示需要的三个元素:

1.ListView:用来展示列表的View,通常是一个xml指定的。例如ListView中经常使用到的"android.R.layout.simple_list_item",就是Android内部定义好的一个xml文件。

2.适配器:用来把数据映射到ListView上。不同的数据对应不同的适配器,如BaseAdapter,ArrayAdapter,CursorAdapter,SimpleAdapter等,他们能够将数组,指针指向的数据,map等映射到view上。也正是由于适配器的存在,listview的使用相当灵活。

3.数据:具体的将被映射的字符串,图片,或者是基本组件。

listview 和适配器的关系如图:


创建一个listview的基本流程如下:

1.获取到listview控件的id

2.初始化适配器

3.为listview位置适配器(setAdapter方法)


下面说一下关于适配器:


适配器的继承关系图如下:


public abstract class BaseAdapter——抽象类,继承它需要实现较多的方法,所以就具有较高的灵活性,实现了ListAdapter和SpinnerAdapter。

BaseAdapter需要重写的方法:

getCount();

getItem(int position);

getItemId(int position);

getView(int position,View convertView,ViewGroup parent);

系统显示列表时,首先实例化一个适配器(这里将手动实例化自定义的适配器)。当手动完成适配时,必须手动映射数据,这需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将显示的是第几行,convertView是从布局文件中inflate来的布局。一般用LayoutInflate的方法将定义好的item.xml文件提取成view实例用来显示。然后将xml文件中的各个组件实例化(使用findViewById方法)。这样便可以将数据对应到各个组件上。但是按钮为了响应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。


ListView优化:

Adapter的作用就是ListView界面与数据之间的桥梁,当列表里的每一项显示到页面的时候,都会调用Adapter的getView方法返回一个View。例如,在列表有1000000000项时,将会占有极大的系统资源。先看如下代码:

public View getView(int position, View convertView, ViewGroup parent)
{
    View item = mInflater.inflate(R.layout.list_item_icon_text, null);
    ((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
    ((ImageView) item.findViewById(R.id.icon)).setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
    return item;
}
如果这么写,后果将不堪设想。原因是有1000000000项列表的时候,每项调用一次getView时,就会产生1000000000个item的view。。。。。再看看下面的代码:

public View getView(int position, View convertView, ViewGroup parent) 
{
    if (convertView == null) 
    {
        convertView = mInflater.inflate(R.layout.item, null);
    }
    ((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
 ((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
 return convertView;
}
上面的代码就明显会好很多(见前面分析ListView的原理时的图),系统将会减少创建很多的view,性能也就得到了极大的提升。。。。然后,还有更好的优化方法:再看看采用ViewHolder模式进行优化:

class ChatListAdapter extends BaseAdapter
{
 static class ViewHolder 
    {
  TextView text;
  ImageView icon;
    }

    public View getView(int position, View convertView, ViewGroup parent) 
    {
     ViewHolder holder;
     if (convertView == null) 
        {
      convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
      holder = new ViewHolder();
      holder.text = (TextView) convertView.findViewById(R.id.text);
      holder.icon = (ImageView) convertView.findViewById(R.id.icon);
            convertView.setTag(holder);
     } 
        else 
        {
      holder = (ViewHolder) convertView.getTag();
     }
        holder.text.setText(DATA[position]);
     holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
     return convertView;
    }
}

ViewHolder的作用:优化显示效率,即之前显示过的不用再从布局中读取,直接从缓存中读取。可以看到它只是一个静态类(1.通常一个普通类不允许声明为静态的,只有内部类才可以。声明为静态的内部类,可以作为一个普通类来使用,而不需要实例一个外部类2.这里设置ViewHolder为static,也就是静态的,静态类只会在第一次加载时会耗费比较长时间,但是后面就可以很好帮助加载,同时保证了内存中只有一个ViewHolder,节省了内存的开销。),它的作用在于减少不必要的调用findViewById,View的findViewById方式是比较耗时的,因此需要考虑值调用一次,然后把对底下的空间引用存在ViewHolder里面,再用convertView.setTag(holder)把它放在view里,下次就可以用(viewholder)convertView.getTag()直接提取了。。。。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值