首页 > 代码库 > 关于BaseAdapter的使用及优化心得
关于BaseAdapter的使用及优化心得
对于Android程序员来说,BaseAdapter肯定不会陌生,灵活而优雅是BaseAdapter最大的特点。开发者可以通过构造BaseAdapter并搭载到ListView或者GridView这类多控件布局上面,实现软件所需要的布局效果。同时,BaseAdapter也是适配器里面最基础的一个类,其他的例如SimpleAdapter、ArrayAdapter都是直接或者间接继承BaseAdapter,所以说学好BaseAdapter基本就熟练掌握了适配器的使用了。
本文需要具备一些Android基础知识的前提下才能更好的理解,因为本文并不是介绍BaseAdapter的基本使用方法,如果对于BaseAdapter不了解可以去参考一下文档。在这里主要是介绍一下BaseAdapter的实现细节和怎么优化BaseAdapter的设计,从而节省我们宝贵的手机运行资源。对于老鸟高手来说,这可能是一个常识了,还是主要给那些像我以前一样刚刚入门的开发者的一点点参考,也希望高手来多多指正。
话不多说,我们看一看某些社交软件的界面(网上找的。。。。。)
这个列表项就是用ListView作为布局,搭载SimpleAdapter或者BaseAdapter作为数据源做成的界面效果。一般来说,很多软件的界面都运用这个方法来实现想要的布局效果。我在这里说明一下后面说到的名词,首先这是一个列表项,里面有各个数据项,例如图片+文件传输助手+test+时间就是一个数据项,它是一个View视图,这张图片里面一共就有8个数据项,由8个View视图按垂直方向排列而成。OK,我们开始步入正题。
假设,我们还是用例子讲解比较好理解,所以呢我们假设这个列表里面一共有40个数据项,而作为手机界面的限制,它一次只能显示8个数据项,同时它的另外32个数据项是隐藏状态。而显示这8个数据项就需要8个View实例,也就是需要实例化8个View的子类来显示内容,此时系统就有8个View实例占据内存空间。那如果我们把手指往下一滑动呢,自然顶端的数据项就会被隐藏,而底端的数据项自然就显示出来,那么这时候需要在new一个新的View实例来吗?如果还是继续创建实例,那么系统内存空间就有9个View实例了,可是我们还是只需要8个View实例就可以在手机屏幕完全显示,而顶端的被隐藏的实例话是可以不存在的了。所以这么来说,这样就浪费了手机的运行资源了。
为了解决这个问题,我们在BaseAdapter中可以对View进行重用,也就是将被隐藏的View改变为将要显示的View并返回出来。
在BaseAdapter中,这个抽象方法非常重要,它就是根据position的值返回对象的数据项的View视图,所以我们主要也是围绕这个方法来达到效率上优化的目的。
public View getView(int position, View convertView, ViewGroup parent) { return null; }
简单介绍一下这个函数的参数,position是ID索引值,也就是ListView的对应的数据项的序号,而convertView则是View视图复用的基础,如果为第一个页面的数据,它的值为null,所以我们可以根据convertView的值来决定我们是否要new一个View的实例出来。
参见下面代码
1 @Override 2 public View getView(int position, View convertView, ViewGroup parent) { 3 if(convertView == null){ 4 convertView = new TextView(context);//新建一个字符串控件 5 ((TextView)convertView).setText(position);//实现其是第几个数据项 6 } 7 else{ 8 ((TextView)convertView).setText(position);//重新修改其是第几个数据项 9 }10 return convertView;11 }
为了简单起来,我这里的View只是一个简简单单的字符串控件,在代码中我们可以看到,先对convertView进行判断,如果其值为null,则说明该方法此时返回的View是第一页的视图,所以需要new出一个View的实例,然后对其进行设置显示的内容并返回(这里设置的内容为position)。如果其值不为null,则说明此时的View视图是可以复用其他已经隐藏的View实例,而convertView的索引就是这个隐藏的实例,自然我们不用重新创建一个新的View实例而直接对convertView进行重新设置数值并返回便可。
这样就保证了程序里面只有足够界面显示的View实例的个数存在,而不会有多余的View来占用系统资源,由此也提高了一点点效率。
这是BaseAdapter的效率优化的一个方面,接下来我们还要说一下另外一个方面,说明白了也就是一个小技巧来提高效率。对于一个BaseAdapter来说,显示的界面肯定不是仅仅的一两个字符串控件(不然我们就用ArrayAdapter了多简便),而一般是加载一个根据layout生成的布局控件,相对来说这个的布局控件会比较复杂,我们会对其的子控件进行获取并赋值返回,所以我们经常要调用findViewById()这个方法来或者子控件的引用。而findViewById()会比较消耗系统资源,频繁的调用在我们快速滑动界面时会加重手机的负担,所以我们也可以对这个地方进行一些小小的改进。
参考这个代码
1 public class PersonAdapter extends BaseAdapter { 2 private List<Person> persons;//绑定的数据 3 private int resource;//绑定的Layout数据项界面 4 private LayoutInflater inflater;//布局填充服务 5 6 public PersonAdapter(Context context, List<Person> persons, int resource) { 7 this.persons = persons; 8 this.resource = resource; 9 inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);10 }11 @Override12 public int getCount() {13 return persons.size();14 }15 @Override16 public Object getItem(int position) {17 return persons.get(position);18 }19 @Override20 public long getItemId(int position) {21 return position;22 }23 @Override24 public View getView(int position, View convertView, ViewGroup parent) {25 TextView nameView = null;26 TextView phoneView = null;27 if(convertView == null){28 convertView = inflater.inflate(resource, null);//生成绑定数据的View界面29 nameView = (TextView) convertView.findViewById(R.id.name);30 phoneView = (TextView) convertView.findViewById(R.id.phone);31 32 ViewCache cache = new ViewCache();33 cache.nameView = nameView;34 cache.phoneView = phoneView; 35 convertView.setTag(cache);36 }else{37 ViewCache cache = (ViewCache) convertView.getTag();38 nameView = cache.nameView;39 phoneView = cache.phoneView;40 }41 Person person = persons.get(position);42 //下面代码实现数据绑定43 nameView.setText(person.getName());44 phoneView.setText(person.getPhone());45 46 return convertView;47 }48 /**49 *保存findViewById获得的子控件索引的内部类50 */51 private final class ViewCache{52 public TextView nameView;53 public TextView phoneView;54 }55 }
重点还是放在getView()这个方法上。在代码里面,我写了一个ViewCache的私有内部类,主要用来保存要来复用的View所需要获得的子控件的索引值。稍微解释一下代码,如果convertView为null,则说明需要new出一个View实例,在这里就是调用Inflater这个系统服务根据resource这个界面的ID值来生成一个View实例,然后用findViewById这个方法来或者其中的子控件的值,并保存在内部类ViewCache中,放入Tag中,setTag(object)是可以放入一个任意的类来保存成为缓存,因为其是Object。最后设置View子控件的内容并返回。如果不为null呢,现在则需要重用View实例了,所以我们也顺带重用一下保存在Tag里面的ViewCache的类成员变量的子控件索引值,而不需要重新去一个一个调用findViewById()这个方法,在界面足够复杂时这样是可以节省很多系统资源的。
这就是BaseAdapter提高效率的两种小技巧,如果大牛有其他更好的技巧,也希望能分享分享用来学习一下。
另外也说一下SimplerAdapter使用遇到的一个小问题,当然也是对于新手来说的。就是关于调用notifyDataSetChanged()但是List里面的数据不更改的问题,其实也就是你的数据源更改出错了,例如下面代码它是不会改变数据的。
List<HashMap<String,Object>> datas = new ArrayList<HashMap<String,Object>>();datas = getData();//getData返回新的数据集adapter.notifyDataSetChanged();//通知数据更新显示,但是List是不会改变数据的显示的
因为在实例化这个Adapter时已经将数据源的引用传进去了,而现在只是修改了数据源data的引用让其重新指向另一个数据集合,所以notifyDataSetChanged()是没反应的,正确的方法应该是这样的。
List<HashMap<String,Object>> datas = new ArrayList<HashMap<String,Object>>();datas.clear();datas.addAll(getData());//getData返回新的数据集,返回类型为List<HashMap<String,Object>>adapter.notifyDataSetChanged();//通知数据更新显示,成功改变
胡乱写了这篇博客,有不足之处还望各位指正。