視圖
-
convertView
系統(tǒng)在繪制ListVIew時,針對每個Item都會調(diào)用一次getView()方法,該方法實(shí)現(xiàn)如下:
//寫法1
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
v = mInflater.inflate(mLayoutId, parent, false);
ItemInfo typeInfo = getItem(position);
TextView tv = (TextView) v.findViewById(R.id.title);
ImageView iv = (ImageView) v.findViewById(R.id.pic);
tv.setText(typeInfo.typeTitle);
iv.setImageResource(typeInfo.typePic);
return v;
}
//---------------------------------------------------------------------------------------
//寫法2
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v;
ViewHolder viewHolder;
ItemInfo item= getItem(position);
if(convertView == null){
v = mInflate.inflate(mLayoutId, parent, false);
viewHolder = new ViewHolder();
viewHolder.tv = (TextView) v.findViewById(R.id.title);
viewHolder.iv = (ImageView) v.findViewById(R.id.pic);
v.setTag(viewHolder);
}else{
v = convertView;
viewHolder = (ViewHolder) v.getTag();
}
viewHolder.tv.setText(item.typeTitle);
viewHolder.iv.setImageResource(item.typePic);
return v;
}
class ViewHolder{
TextView tv;
ImageView iv;
}
觀察上述兩種寫法,如果我們不采用convertView的話,Adapter擁有多少個數(shù)據(jù)就需要Inflate多少個Layout這是非常消耗資源的。而對于ListView而言,同時只需持有屏幕上能顯示的最大條目數(shù)的View(n條)即可。通過復(fù)用convertView的方式,只需執(zhí)行n次inflate即可,之后則可以只做數(shù)據(jù)更新操作。
PS: 屏幕上最大能顯示的條目數(shù)可能不是剛開始顯示在屏幕上的完整條目數(shù),可能頂部顯示半個item,底部顯示半個item,此時顯示的條目數(shù)才是最多的。
-
ViewHolder
優(yōu)化代碼如上所示,和復(fù)用convertView類似。我們在創(chuàng)建view的同時創(chuàng)建一個ViewHolder對象并執(zhí)行findViewById()對其成員進(jìn)行初始化,然后通過View類的setTag()方法將該ViewHolder對象與view綁定。當(dāng)我們需要更新數(shù)據(jù)時,通過View類的getTag()的方法獲取與控件綁定的ViewHolder對象,然后就可以得到需要更新數(shù)據(jù)的控件ID,這樣就可以減少對findViewById的調(diào)用,優(yōu)化數(shù)據(jù)更新時的效率。
數(shù)據(jù)
-
分批加載
假設(shè)我們擁有的數(shù)據(jù)源是一個包含有幾萬條數(shù)據(jù)的List,此時如果在ListView創(chuàng)建時一次性就將所有的數(shù)據(jù)加載進(jìn)來,就可能造成程序卡頓等現(xiàn)象。此時我們可以通過分批加載的方式來加載數(shù)據(jù):比如屏幕上最多可以顯示8條數(shù)據(jù),在初始化時我們一次加載20條數(shù)據(jù)。當(dāng)用戶將listview滾動到最底部時,程序執(zhí)行加載數(shù)據(jù)操作,再次加載50條數(shù)據(jù),并更新Adapter,這樣用戶單次等待時間就是加載50條數(shù)據(jù)的時間而不是幾萬條數(shù)據(jù)的時間。
實(shí)現(xiàn)分批加載的重要實(shí)現(xiàn)代碼如下:
//maxDataSize:數(shù)據(jù)源包含的總數(shù)據(jù)條目
//isLoading:用于判斷是否處于load狀態(tài)
//mData:數(shù)據(jù)源數(shù)據(jù)
//mFooterView:用于顯示加載中的信息提示
//---------------------------------------------------------------------------------------
/**一次加載50條數(shù)據(jù)
* @param totalCount 當(dāng)前數(shù)據(jù)的條目數(shù)
*/
private ArrayList<ItemInfo> getNewData(int totalCount) {
ArrayList<ItemInfo> data = new ArrayList<ItemInfo>();
//如果加載的條目剩余不足50條
if(totalCount + addCount > maxDataSize){
for(int i = totalCount; i < maxDataSize; i++)
data.add(new ItemInfo("這是第"+i+"條列表項", R.drawable.ic_launcher));
}else{
for(int i = 1; i <= 50; i++)
data.add(new ItemInfo("這是第"+(i+totalCount)+"條列表項", R.drawable.ic_launcher));
}
return data;
}
//對ListView設(shè)立Scroll事件監(jiān)聽,并重寫onScroll方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, final int totalItemCount) {
// TODO Auto-generated method stub
Log.d("Optimize", "first:"+ firstVisibleItem+" visible:"+ visibleItemCount
+ " total:" + totalItemCount);
int lastItemId = view.getLastVisiblePosition();
//已經(jīng)顯示了最后一條數(shù)據(jù)
if(lastItemId == totalItemCount -1){
if(totalItemCount >= maxDataSize){
Toast.makeText(this, "沒有更多的數(shù)據(jù)了", Toast.LENGTH_SHORT).show();
}
//數(shù)據(jù)還沒有完全加載完,并且不處于load狀態(tài)
else if(!isLoading){
mFooterView.setVisibility(View.VISIBLE);
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
isLoading = true;
final ArrayList<ItemInfo> newData = getNewData(totalItemCount-1);
//這里采用延時的方式是為了模擬數(shù)據(jù)加載的過程,假設(shè)數(shù)據(jù)加載用了3s
mHandler.postDelayed(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
//adapter的數(shù)據(jù)更新必須放在UI Thread中否則可能會報這樣的錯誤:
//java.lang.IllegalStateException:
//The content of the adapter has changed but ListView did not receive a notification.
mData.addAll(newData);
mAdapter.notifyDataSetChanged();
if(mListView.getFooterViewsCount() != 0)
mFooterView.setVisibility(View.GONE);
isLoading = false;
}
}, 3000);
}
}).start();
}
}
}
-
分頁加載
采用分批加載的方式主要是優(yōu)化了ListView加載速度,而假設(shè)我們擁有幾百萬條數(shù)據(jù),如果我們只是單純的執(zhí)行分批加載,到最后我們的Adapter將持有一個擁有幾百萬條數(shù)據(jù)的List,這將占用極大的內(nèi)存??!
此時我們可以通過分頁加載的方式來優(yōu)化內(nèi)存占用。分頁加載和分批加載類似,只是在加載第二批數(shù)據(jù)時對第一批數(shù)據(jù)進(jìn)行了清除操作。
分頁加載的重要實(shí)現(xiàn)代碼如下:
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, final int totalItemCount) {
// TODO Auto-generated method stub
Log.d("Optimize", "first:"+ firstVisibleItem+" visible:"+ visibleItemCount
+ " total:" + totalItemCount);
int lastItemId = view.getLastVisiblePosition();
//已經(jīng)顯示了最后一條數(shù)據(jù)
if(lastItemId == pageAddCount -1){
if(totalLoadedItem >= maxDataSize){
Toast.makeText(this, "沒有更多的數(shù)據(jù)了", Toast.LENGTH_SHORT).show();
}
//數(shù)據(jù)還沒有完全加載完,并且不處于load狀態(tài)
else if(!isLoading){
mFooterView.setVisibility(View.VISIBLE);
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
isLoading = true;
final ArrayList<ItemInfo> newData = getNewData(totalLoadedItem-1);
//這里采用延時的方式是為了模擬數(shù)據(jù)加載的過程,假設(shè)數(shù)據(jù)加載用了3s
mHandler.postDelayed(new Runnable(){
@Override
public void run() {
mData.clear();
mData.addAll(newData);
mAdapter.notifyDataSetChanged();
mListView.setSelection(0);
if(mListView.getFooterViewsCount() != 0)
mFooterView.setVisibility(View.GONE);
isLoading = false;
}
}, 3000);
}
}).start();
}
}
}