Android中AdapterView/Adapter的深度学习

BaseAdapter的深度学习

  博主工作了几年,也用了几年的ListView等AdapterView控件,但关于Adapter的一些问题并没有深入下去,终于有时间学习总结下关于BaseAdapter的一些较深入的话题。本文涉及三个话题:Adapter的回收机制和效率提升,getItemViewType()/getViewTypeCount()方法以及notifyDatasetChanged()使用的注意点。

1.Adapter的回收机制和效率提升

  Android在绘制Adapter时,系统首先调用getCount()方法,根据它的返回值得到ListView的长度,然后根据这个长度,调用getView()方法逐行绘制。如果ListView的长度超过了屏幕的长度,android只会绘制显示出来的Item,同时,系统会回收走隐藏的Item。

  如下图所示,此时系统绘制的只有position:4到positon12这9个Item.若按箭头方法滑动,将回收position12,以及绘制position3.

Android中AdapterView/Adapter的深度学习

  总的来说,显示出来然后因为拖动而被隐藏的Item才会触发回收。在方法getView(int position, View convertView, ViewGroup parent)中,第二个参数convertView的含义:是代表系统最近回收的View。若整屏能显示9个Item,第一次打开带ListView的控件时,因为并没有回收的View,调用getVIew时,参数convertView的值会为null,否则将不是null,而是最近回收的View的引用.那么合理利用convertView将是提升Adapter效率的关键,否则将会产生大量的new View开销。

 1 @Override

 2     public View getView(int position, View convertView, ViewGroup parent) 

 3     {

 4         Holder1 holder1 = null;

 5         if(null==convertView)    

 6         {

 7             System.out.println("convertView == null" + " position:" + position);

 8             holder1=new Holder1();    

 9             convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null);

10             holder1.textView=(TextView)convertView.findViewById(R.id.textview);    

11             convertView.setTag(holder1);

12         }    

13         else    

14         {

15             holder1=(Holder1)convertView.getTag();

16             System.out.println("重用:" + holder1.textView.getText());

17         }

18         holder1.textView.setText("position: "+position);   

19         return convertView;             

20     }  

21     

22     class Holder1    

23     {

24         public TextView textView;    

25     }

说明一下上图中的例子,按箭头方法拖动,接下来将显示position=4的Item,系统调用getView方法时,第二个参数convertView的值将是position=12的View的引用(最近回收的一个Item的View).[读者可在convertView中用一个TextView记录下每个View的position值,就可发现这个规律]
精致的逻辑说明:系统绘制Item的View和回收Item的View时有个规则:该Item只要显示出一点点就触发绘制,但必须等该Item完全隐藏之后才触发回收。试验上例时若结果对不上请注意这条说明。

2.getItemViewType()/getViewTypeCount()方法

  若果Item的View都是同一个模板则用不到这俩方法,但很多情况下我们的AdapterView中可能会用到2个或以上的不同的模板,那这些不同的模板如何复用,那就是这俩方法的作用。

  看下官方对convertView的解释:The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)). 意思大概就是: 在有些AdapterView的使用中,比如微博中 有的item中包含图片 有的item包含视频 那么必然的 我们需要用到2种item的布局方式此时如果只是单纯判断“null==convertView”,会造成回收的view不符合你当前需要的布局 而类似转换失败出错退出。

  代码示例:

 1 @Override

 2     public View getView(int position, View convertView, ViewGroup parent) 

 3     {

 4         Holder1 holder1 = null;

 5         Holder2 holder2 = null;

 6         int type = getItemViewType(position);

 7         if(null==convertView)    

 8         {

 9             switch (type) {

10             case TYPE_1:

11                 System.out.println("convertView == null" + " position:" + position);

12                 holder1=new Holder1();    

13                 convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null);

14                 holder1.textView=(TextView)convertView.findViewById(R.id.textview);    

15                 convertView.setTag(holder1);

16                 break;

17             case TYPE_2:

18                 System.out.println("convertView == null" + " position:" + position);

19                 holder2=new Holder2();    

20                 convertView=LayoutInflater.from(mContext).inflate(R.layout.imageview, null);

21                 holder2.imageView=(ImageView)convertView.findViewById(R.id.imageview);    

22                 convertView.setTag(holder2);

23                 break;

24             default:

25                 break;

26             }

27         }    

28         else    

29         {

30             switch (type) {

31             case TYPE_1:

32                 holder1=(Holder1)convertView.getTag();

33                 System.out.println("重用:" + holder1.textView.getText());

34                 break;

35             case TYPE_2:

36                 holder2=(Holder2)convertView.getTag();

37                 break;

38             default:

39                 break;

40             }

41         }

42         switch (type) 

43         {

44         case TYPE_1:

45             holder1.textView.setText("position: "+position);   

46             break;

47         case TYPE_2:

48             holder2.imageView.setBackgroundColor(colors[position%6]);

49             break;

50         default:

51             break;

52         }

53         return convertView;             

54     }  

55     

56     class Holder1    

57     {

58         public TextView textView;    

59     }

60     

61     class Holder2

62     {

63         public ImageView imageView;

64     }

65 

66     @Override

67     public int getItemViewType(int position) 

68     {

69         if(position < 2)

70             return TYPE_1;

71         else if(position%2==0)

72             return TYPE_1;

73         else

74             return TYPE_1;

75     }

76 

77     @Override

78     public int getViewTypeCount() 

79     {

80         return 2;

81     }
View Code

  Android中AdapterView/Adapter的深度学习

这个例子中有两点需要说明:

  1.在getItemTypeView()方法中的返回值不是随便设置的,在SDK中有句话“Note: Integers must be in the range 0 to getViewTypeCount() - 1”。也就是说:返回值得返回必须是0 - (getViewTypeCount()-1)范围内。

  2.关于setTag()和getTag()的理解:初学者对这两个方法可能不能很好的理解,调用setTag("")方法时,可以理解为为View设置了一个标识,然后通过getTag()来获取标识,或者理解为View作为一个容器除了显示一些字符串,图片之外,还可以通过setTag("")方法往其中存放一些数据,然后通过通过getTag()来获取数据。

  说明:我自己在学习这个知识点的过程中,产生了一个奇怪问题:我不用继承父类的这两个方法,自定义方法也可以完成这个功能,想通了之后发现时钻了牛角尖,就不讨论这个问题,若读者也产生了这个问题,可留言交流。

3.notifyDatasetChanged()使用

  首先看SDK中的说明:Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself. 这句话也好理解。我们在使用该方法过程中,有时候会发现不生效。

1 //我们通常通过构造器将list赋给自定义的Adapter

2     ArrayList<String> list = new ArrayList<String>();

3     MyAdapter adapter = new MyAdapter(context,list);

4     

5     list = query(...);

6     adapter.notifyDataSetChanged();

这时notifyDataSetChanged()是不会生效的,应该改为:

1 list.clear();

2 list.addAll(query(...));

3 adapter.notifyDataSetChanged();

这不是android的问题,而是Java特性相关的问题。Java语言的变量中存的是引用。使用"list=query(...);"时,效果是改变了list的引用,而MyAdapter中使用的还是原来的引用,所以notifyDataSetChanged()时不能生效。正确的做法是通过方法来操作对象本身,而不是改变其引用。

Demo百度云链接:http://pan.baidu.com/s/1dDAAVhZ (找不到更好的链接方式,如果有更好地方式请留言告诉我。)

Author:Andy Zhai
2014-01-01  19:20:52

 

 

 

  

  

你可能感兴趣的:(android)