前言
Adapter用于存放数据。
Android是完全遵循MVC模式设计的框架,Activity是Controller,layout是View
因为layout五花八门,很多数据都不能直接绑定上去,所以Android引入了Adapter这个机制作为复杂数据的展示的转换载体,所以各种Adapter只不过是转换的方式和能力不一样而已。 Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。在常见的View(List View,Grid View)等地方都需要用到Adapter。
之前我提过各种Adapter,但是有时特殊的需求没法满足我们。
我有过这样一个需求,需要在ListView中动态显示每一行的状态,状态是连接IP地址的状态,起始的时候每一行的有一个状态图是等待的那种。如果连接上了就显示对号,如果没有就显示错号。而且每一行对IP连接都是异步的。如何实现呢。
最初的思路是获取数据数组,将他们组装成Adapter放入ListView。然后开启多线程,去连接每一个Item的IP地址,当一个线程连接成功后,通过Handler机制,改变原始放入Adapter的数组的对应属性,并通过notifyDataSetChanged()方法更新adapter,从而动态更新整个ListView。
1、自定义Adapter,添加数据判断
public class MyAdapter extends BaseAdapter {
private Context context;
private List<ipInfo> data;
public MyAdapter(Context context,List<ipInfo> data){
this.context=context;
this.data=data;
}
@Override//返回数据集的长度
public int getCount() {
// TODO Auto-generated method stub
return data.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return data.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if(convertView==null){//不是一次更新,则填充一个新视图
convertView= LayoutInflater.from(context).inflate(R.layout.info_item_layout, null);
holder = new ViewHolder();
holder.imgTv = (ImageView) convertView.findViewById(R.id.img);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.id = (TextView) convertView.findViewById(R.id.ipid);
holder.ip = (TextView) convertView.findViewById(R.id.ip);
holder.state = (TextView) convertView.findViewById(R.id.state);
holder.probar = (ProgressBar) convertView.findViewById(R.id.proBar);
holder.imgSuc = (ImageView) convertView.findViewById(R.id.success);
holder.imgErr = (ImageView) convertView.findViewById(R.id.error);
convertView.setTag(holder);
}else { //else里面说明,convertView已经被复用了,说明convertView中已经设置过tag了,即holder
holder = (ViewHolder) convertView.getTag();
}
ipInfo bean = data.get(position);
holder.name.setText(bean.getIpname());
holder.ip.setText(bean.getIp());
holder.id.setText(bean.getIpid());
holder.state.setText(bean.getIpstate());
if(bean.getIpstate().equals("1")){//加载时的图标
holder.probar.setVisibility(View.VISIBLE);
holder.imgSuc.setVisibility(View.GONE);
holder.imgErr.setVisibility(View.GONE);
}
else if(bean.getIpstate().equals("2")){//成功时的图标
holder.probar.setVisibility(View.GONE);
holder.imgSuc.setVisibility(View.VISIBLE);
holder.imgErr.setVisibility(View.GONE);
}
else if(bean.getIpstate().equals("3")){//错误时的图标
holder.probar.setVisibility(View.GONE);
holder.imgSuc.setVisibility(View.GONE);
holder.imgErr.setVisibility(View.VISIBLE);
}
return convertView;
}
//这个ViewHolder只能服务于当前这个特定的adapter,因为ViewHolder里会指定item的控件,不同的ListView,item可能不同,所以ViewHolder写成一个私有的类
private class ViewHolder {
ImageView imgTv;
TextView name;
TextView id;
TextView ip;
TextView state;
ProgressBar probar;
ImageView imgSuc;
ImageView imgErr;
}
}
1、要自定义Adapter时,首先要继承BaseAdapter。然后根据需要重写getView()方法,该方法主要是找到item的布局文件,然后根据你传到自定义Adapter中的数据信息进行对应设置,以此来进行适配
convertView= LayoutInflater.from(context).inflate(R.layout.info_item_layout, null);//绑定对应的Itmebuj
将converView的布局传给ViewHolder,data是Activity传过来的内容数据,利用ViewHolder设置数据。根据出过来的代码编号不同来显示不同的图片。
其中holder = (ViewHolder)converView.getTag();是为了防止多次findViewById来拖慢程序运行效率
Android中有个叫做Recycler的构件,下图是他的工作原理:

- 如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
- ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
- 当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。
使用ConvertView和ViewHolder的优化是针对ListView的Adapter(BaseAdapter)的。这种优化的 优点 如下:
1) 重用了ConveertView,在很大程度上减少了内存的消耗。通过判断ConvertView是否为NULL,如果是NULL那么就需要生成一个新的View出来(通过LayoutInflater生成),绑定数据后显示给用户;如果ConvertView不是NULL,则我们需要做的就只有绑定数据并呈现给用户。
2) 由于ListView中的Item往往都是只有一个模板,即整个ListView的所有Item用的都是一套ID,所以我们可以把findViewById()方法提取出来,即全程之找一遍ID,这样可以避免让程序不停的做同样的事情。这样做的话通常需要用到另一个内部类(通常写成ViewHolder,在这个类中定义Item中需要用到的控件的名称),这样做也可以方便我们调用控件的onClick()事件等等。
3) 综上,这样做不仅减少了项目性能的消耗,也减少了内存的消耗。
2、开启线程,利用Handler来更新ListView
首先,数据传递给MyAdapter,每个状态都是显示“加载时的图标”。然后线程进行连接检查,将能连上的和不能连上的进行更新。
//将数组中的数据放入到自定义的adpater
adapter = new MyAdapter(this,datas);
lv_data = (ListView)findViewById(R.id.list_view) ;
//写入listview
lv_data.setAdapter(adapter);
helper.close();
//加载完列表以后进行验证ip是否在线的操作
//加载每一个ip去ping
for(int i =0 ; i< datas.size();i++){
String ip = datas.get(i).getIp();
pingInNewThread(ip,""+""+i);//有一个ip就开一个线程。。将ip地址和listview的地址放入到线程中
}
//开启线程验证每一个IP是否可用
private void pingInNewThread(final String ip ,final String ps) {
Thread th = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//进行耗时连接操作,连接成功与否都向Handler sendMessage
}
});
th.start();
}
对应的Handler
Handler mHandler = new Handler() {//等待Message的穿过来,用于动态更新ping的状态图标。这个是
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
String arr[] = msg.obj.toString().split(",");
String ip = arr[0];
String ps = arr[1];
int pos = Integer.parseInt(ps);
switch (msg.what) {
case 0:
// Toast.makeText(MainActivity.this, ip + "拼不通 位置是=" + ps,Toast.LENGTH_SHORT).show();
datas.get(pos).setIpstate("3");
adapter.notifyDataSetChanged();
break;
case 1:
if (FLAG_FIRST == 0) {
// Toast.makeText(MainActivity.this,ip + "获得第一名位,置是=" + ps, Toast.LENGTH_SHORT).show();
} else {
// Toast.makeText(MainActivity.this,ip + "获得" + (FLAG_FIRST + 1) + "名,位置是=" + ps,Toast.LENGTH_SHORT).show();
}
datas.get(pos).setIpstate("2");
adapter.notifyDataSetChanged();
FLAG_FIRST++;
break;
case 2:
// Toast.makeText(MainActivity.this, "发生本地异常", Toast.LENGTH_SHORT) .show();
datas.get(pos).setIpstate("2");
adapter.notifyDataSetChanged();
break;
default:
break;
}
}
};
data是MyAdapter的那个数据源,msg获取的信息是哪个位置上的测试连接完成。这样再Handler接收到后就能对对应的数据更改值,之后调用 notifyDataSetChanged();就能刷新adapter,调用getView重新加载数据,更新ListView