前言:对于listView的BaseAdapter的派生,难度比较大。最难理解的莫过于getView(int position, View convertView, ViewGroup parent)这个函数是如何产生每条记录的,有些博客中利用holderView,有些博客却没有用,种种方法之间有什么异同,今天我们就来揭开这个绘制ITEM机制的面纱。
本篇借助《PullToRefresh使用详解(二)---重写BaseAdapter实现复杂XML下拉刷新》的例子。所以本篇对于代码的讲解就比较粗略,如果有读者对于如何重写BaseAdapter不太熟悉的话,请先移步看看这篇文章,然后再回来这里,相信会有不一样的收获。
工作原理:
1、ListView 针对List中每个item,要求 adapter “给我一个视图” (getView)。
2、一个新的视图被返回并显示
如果我们有上亿个项目要显示怎么办?为每个项目创建一个新视图?NO!这不可能!
实际上Android为你缓存了视图。Android中有个叫做Recycler的构件,下图是他的工作原理:
如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。 以上摘自《ListView中getView的原理+如何在ListView中放置多个item》
也就是说:
1、android的listView在初始化的时候,如上面这个列表,整个屏幕只能放下7个item,那么listView在初始化时,就会只创建7个view,对于这些view也就是参数中的convertView。
2、那问题来了,当继续网上滑动,item1消失了,而item8出来了。那系统还是为item8重新创建一个新的convertView吗?另一个问题,item1的convertView去哪了?(销毁回收资源,还是重新利用?)如果你是系统设计者,你会怎么做?大家想想,如果为每个要显示的item都创建新convertView是不是太浪费了,况且对于item1的convertView已经没用了,我们何不把它拿来给item8用。对!系统就是这样做的!这就是convertView的回收机制。就是将那些不再被用的ITEM的convertView重新给即将显示的ITEM使用的机制!
先给大家看一下单个ITEM的布局图片,对于具体布局代码,看源码吧。
对于JAVA源码,我们先看这种方式写的convertView的生成方法。
package com.example.try_pulltorefresh_map; /** * 完成了从TXT文本中提取,并向下刷新 * blog:http://blog.csdn.net/harvic880925 * @author harvic * @date 2014-5-8 * */ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import org.json.JSONArray; import com.handmark.pulltorefresh.library.PullToRefreshBase; import com.handmark.pulltorefresh.library.PullToRefreshListView; import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode; import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener; import android.os.Bundle; import android.app.ListActivity; import android.content.Context; import android.graphics.Color; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; public class MainActivity extends ListActivity { private ArrayList<HashMap<String, Object>> listItem = new ArrayList<HashMap<String, Object>>(); private PullToRefreshListView mPullRefreshListView; MyAdapter adapter=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPullRefreshListView = (PullToRefreshListView) findViewById(R.id.pull_refresh_list); //设定下拉监听函数 mPullRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() { @Override public void onRefresh(PullToRefreshBase<ListView> refreshView) { String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL); // Update the LastUpdatedLabel refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label); // Do work to refresh the list here. } }); mPullRefreshListView.setMode(Mode.PULL_FROM_END);//设置底部下拉刷新模式 listItem=getData();//获取LIST数据 adapter = new MyAdapter(this); //设置适配器 ListView actualListView = mPullRefreshListView.getRefreshableView(); actualListView.setAdapter(adapter); } private ArrayList<HashMap<String, Object>> getData() { ArrayList<HashMap<String, Object>> list = new ArrayList<HashMap<String, Object>>(); HashMap<String, Object> map = new HashMap<String, Object>(); InputStream inputStream; try { inputStream=this.getAssets().open("my_home_friends.txt"); String json=readTextFile(inputStream); JSONArray array = new JSONArray(json); for (int i = 0; i < array.length(); i++) { map = new HashMap<String, Object>(); map.put("name", array.getJSONObject(i).getString("name")); map.put("info", array.getJSONObject(i).getString("info")); map.put("img",array.getJSONObject(i).getString("photo")); list.add(map); } return list; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return list; } public final class ViewHolder{ public TextView name; public TextView info; public TextView attentntion; } public class MyAdapter extends BaseAdapter{ private LayoutInflater mInflater; public MyAdapter(Context context){ this.mInflater = LayoutInflater.from(context); } @Override public int getCount() { // TODO Auto-generated method stub return listItem.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int arg0) { // TODO Auto-generated method stub return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("position:"+position+" convertView:"+convertView); ViewHolder holder = null; holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention); holder.name.setText((String)listItem.get(position).get("name")); holder.info.setText((String)listItem.get(position).get("info")); final TextView attention=holder.attentntion; holder.attentntion.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub attention.setTextColor(Color.RED); } }); convertView.setTag(holder); return convertView; } } ////工具类 /** * * @param inputStream * @return */ public String readTextFile(InputStream inputStream) { String readedStr = ""; BufferedReader br; try { br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String tmp; while ((tmp = br.readLine()) != null) { readedStr += tmp; } br.close(); inputStream.close(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return readedStr; } }
@Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("position:"+position+" convertView:"+convertView); ViewHolder holder = null; holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention); holder.name.setText((String)listItem.get(position).get("name")); holder.info.setText((String)listItem.get(position).get("info")); final TextView attention=holder.attentntion; holder.attentntion.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub attention.setTextColor(Color.RED); } }); return convertView; }
这里利用system.out.println对convertView进行捕捉,运行如果如下:
手机初始化是这样子的:
根据上面我们讲的理论,在初始化时,整个屏幕能放下三个ITEM,所以会创建三个全新的convertView。当我往下拉一个ITEM,出现第四个ITEM的时候,就会回收第一个ITEM的convertView给第四个。捕捉结果如下:
清楚的看到,前四个convertView为NULL,当第五个ITEM出现时,此时由于第一个ITEM肯定已经滚出屏幕,所以将其重新传给即将出现的item5使用。我们上面说的第四个ITEM出现的时候就应该不再创建新convertView了,我想android开发者在考虑多创建一个ITEM的目的在于更安全吧。
回到上面的代码,好像看着代码没有任何问题,我在里面写了个clickListener,当点击“关注”的时候,字体会变红,试一下。
点击“关注” 下拉后再拉回来
问题出现了:当拉回来的时候,“关注”不再红了!!!!!!为什么????
问题出在代码上:
holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention);每次运行getView获取当前ITEM时,都会重新new 一个viewHolder与R.layout.item绑定,也就是说,每次都会产生一个新布局赋值给convertView让其显示。而我们上面讲了,android会将回收过来的convertView返回给即将显示的getView使用,以节约资源。而我们这里却没有领情,每次都重新创建一个布局赋给convertView,由于每次都创建一个新布局,所以当ITEM1被重新拉回来显示的时候,由于是重新创建的布局,当然是初始状态。“关注”当然也就是黑色的了。
改进
@Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("position:"+position+" convertView:"+convertView); ViewHolder holder = null; if (convertView == null) { holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } holder.name.setText((String)listItem.get(position).get("name")); holder.info.setText((String)listItem.get(position).get("info")); final TextView attention=holder.attentntion; holder.attentntion.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub attention.setTextColor(Color.RED); } }); return convertView; }
ViewHolder holder = null; if (convertView == null) { holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); }
看第二张图,当拉下去再拉回来的时候,一切正常,但当我们再往下拉(第三张图),问题又出现了,明明没有点P-5,为什么关注反而是红色的!!!!!!
这是因为,P-5用的是P-1回收来的convertView!!!而P-1的convertView的布局里“关注”是红色的。所以只要回收机制在,我们就没有办法改变从P-1回收来的convertView里的图片布局,除非人为的将其重置!
理解了这个问题以后,我们想想解决办法。
首先申请一个arrayList attentionArr变量,保存用户点击“关注”的ITEM的position,然后在绘制当前ITEM时,根据这个position是否在attentionArr里来判断是不是将“关注”重新变红。
代码如下:
@Override public View getView(final int position, View convertView, ViewGroup parent) { System.out.println("position:"+position+" convertView:"+convertView); ViewHolder holder = null; if (convertView == null) { holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.name = (TextView)convertView.findViewById(R.id.name); holder.info = (TextView)convertView.findViewById(R.id.info); holder.attentntion=(TextView)convertView.findViewById(R.id.attention); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } holder.name.setText((String)listItem.get(position).get("name")); holder.info.setText((String)listItem.get(position).get("info")); final TextView attention=holder.attentntion; //根据当前position判断,重新制做样式 if (attentionArr.contains(position)) { attention.setTextColor(Color.RED); }else { attention.setTextColor(Color.BLACK); } holder.attentntion.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub attention.setTextColor(Color.RED); attentionArr.add(position);//在点击时将position加入其中 } }); return convertView; }