前言
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