您好,登錄后才能下訂單哦!
首先,讓我們來了解下系統(tǒng)時怎么繪制listview的:
ListView繪制的過程如下:
首先,系統(tǒng)在繪制ListView之前,將會先調(diào)用getCount方法來獲取Item的個數(shù)。
之后每繪制一個Item就會調(diào)用一次getView方法,在此方法(getView)內(nèi)就可以引用事先定義好的xml來確定顯示的效果并返回一個View對象作為一個Item顯示出來。也正是在這個過程中完成了適配器的主要轉(zhuǎn)換功能,把數(shù)據(jù)和資源以開發(fā)者想要的效果顯示出來。也正是getView的重復(fù)調(diào)用,使得ListView的使用更為簡單和靈活。這兩個方法是自定ListView顯示效果中最為重要的,同時只要重寫好了就兩個方法,ListView就能完全按開發(fā)者的要求顯示。而getItem和getItemId方法將會在調(diào)用ListView的響應(yīng)方法的時候被調(diào)用到。所以要保證ListView的各個方法有效的話,這兩個方法也得重寫。比如:沒有完成getItemId方法的功能實現(xiàn)的話,當(dāng)調(diào)用ListView的getItemIdAtPosition方法時將會得不到想要的結(jié)果,因為該方法就是調(diào)用了對應(yīng)的適配器的getItemId方法。
Adapter :
Adapter是連接后端數(shù)據(jù)和前端顯示的適配器接口,是數(shù)據(jù)和UI(View)之間一個重要的紐帶。在常見的View(ListView,GridView)等地方都需要用到Adapter。如下圖直觀的表達(dá)了Data、Adapter、View三者的關(guān)系:
Adapter的類有好幾個,直接看圖:
Android ListView理解,BaseAdapter
ListView是Android開發(fā)過程中較為常見的組件之一,它將數(shù)據(jù)以列表的形式展現(xiàn)出來。一般而言,一個ListView由以下三個元素組成:
1.View,用于展示列表,通常是一個xml所指定的。大家都知道Android的界面基本上是由xml文件負(fù)責(zé)完成的,所以ListView的界面也理所應(yīng)當(dāng)?shù)氖褂昧藊ml定義。例如在ListView中經(jīng)常用到的“android.R.layout.simple_list_item_1”等,就是Android系統(tǒng)內(nèi)部定義好的一個xml文件。
2.適配器,用來將不同的數(shù)據(jù)映射到View上。不同的數(shù)據(jù)對應(yīng)不同的適配器,如ArrayAdapter,CursorAdapter,SimpleAdapter等,他們能夠?qū)?shù)組,指針指向的數(shù)據(jù),Map等數(shù)據(jù)映射到View上。也正是由于適配器的存在,使得ListView的使用相當(dāng)靈活,經(jīng)過適配器的處理后,在view看來所有的數(shù)據(jù)映射過來都是一樣的。
3.數(shù)據(jù),具體的別映射的數(shù)據(jù)和資源,可以是字符串,圖片等,通過適配器,這些數(shù)據(jù)將會被現(xiàn)實到ListView上。所有的數(shù)據(jù)和資源要顯示到ListView上都通過適配器來完成。
系統(tǒng)已有的適配器可以將基本的數(shù)據(jù)顯示到ListView上,如:數(shù)組,Cursor指向的數(shù)據(jù),Map里的數(shù)據(jù)。但是在實際開發(fā)中這些系統(tǒng)已實現(xiàn)的適配器,有時不能滿足我們的需求。而且系統(tǒng)自帶的含有多選功能ListView在實際使用過程中會有一些問題。要實現(xiàn)復(fù)雜的ListView可以通過繼承ListView并重寫相應(yīng)的方法完成,同時也可以通過繼承BaseAdapter來實現(xiàn)。通過文檔可以看出,ArrayAdapter,CursorAdapter,SimpleAdapter都繼承于BaseAdapter。所以通過繼承BaseAdapter就可以完成自己的Adapter,可以將任何復(fù)雜組合的數(shù)據(jù)和資源,以任何你想要的顯示效果展示給大家。
繼承BaseAdapter之后,需要重寫以下四個方法:getCount,getItem,getItemId,getView。
在getView中可以完成自己想要的界面布局
具體的實現(xiàn)方法如下
public ManagerAdapter(List<User> mRegulatorList, Context context, DataBase mDataBase) {
this.mRegulatorList = mRegulatorList;
this.mContext = context;
this.mDataBase = mDataBase;
}
public int getCount() {
return (this.mRegulatorList.size());
}
public Object getItem(int position) {
return (this.mRegulatorList.get(position));
}
public long getItemId(int position) {
return (position);
}
/**
* 加載xml的條目,實現(xiàn)數(shù)據(jù)的初始化,為自己的控件設(shè)置監(jiān)聽事件
* @param position
* @param contentview
* @param arg2
* @return
*/
public View getView(int position, View contentview, ViewGroup arg2) {//加載XML視圖文件
ViewHolder holder;
this.regulator = this.mRegulatorList.get(position);
//如果視圖之間沒有加載過,就加載xml
if (contentview == null) {
contentview = LayoutInflater.from(this.mContext).inflate(R.layout.manager_items, null);
holder = new ViewHolder();
this.bindID(contentview, holder);
//保存視圖狀態(tài)
contentview.setTag(holder);
} else {
holder = (ViewHolder) contentview.getTag();
this.setItemInfo( holder, position, mDataBase);
this.setClickListener(holder, position, mDataBase);
}
return contentview;
}
/**
* 設(shè)置一個容器用于存放控件
*/
public class ViewHolder {
TextView txtManagerItemMarkName;
TextView txtManagertmeLastTouchTime;
TextView txtManagerItemLastLocation;
TextView txtManagertgetDeviceName;
ImageView imgManagerItemUserHead;
Button btnManagerItemPhone;
Button btnManagerItemPosition;
}
/**
* 綁定視圖ID,holder是組件容器
*
* @param contentview
* @param holder
*/
private void bindID(View contentview, ViewHolder holder) {
holder.imgManagerItemUserHead = (ImageView) contentview.findViewById(R.id.manageractivity_imv1);
holder.txtManagerItemLastLocation = (TextView) contentview.findViewById(R.id.manageractivity_txt2_location);
holder.txtManagerItemMarkName = (TextView) contentview.findViewById(R.id.manageractivity_txt2_name);
holder.txtManagertgetDeviceName = (TextView) contentview.findViewById(R.id.manageractivity_txt2_name);
holder.txtManagertmeLastTouchTime = (TextView) contentview.findViewById(R.id.manageractivity_txt3_time);
holder.btnManagerItemPhone = (Button) contentview.findViewById(R.id.manageractivity_btn_phone);
holder.btnManagerItemPosition = (Button) contentview.findViewById(R.id.manageractivity_btn_position);
}
ListView 針對每個item,要求 adapter “返回一個視圖” (getView),也就是說ListView在開始繪制的時候,系統(tǒng)首先調(diào)用getCount()函數(shù),根據(jù)他的返回值得到ListView的長度,然后根據(jù)這個長度,調(diào)用getView()一行一行的繪制ListView的每一項。如果你的getCount()返回值是0的話,列表一行都不會顯示,如果返回1,就只顯示一行。返回幾則顯示幾行。如果我們有幾千幾萬甚至更多的item要顯示怎么辦?為每個Item創(chuàng)建一個新的View?不可能!??!實際上Android早已經(jīng)緩存了這些視圖,大家可以看下下面這個截圖來理解下,這個圖是解釋ListView工作原理的最經(jīng)典的圖了大家可以收藏下,不懂的時候拿來看看,加深理解,其實Android中有個叫做Recycler的構(gòu)件,順帶列舉下與Recycler相關(guān)的已經(jīng)由Google做過N多優(yōu)化過的東東比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不難理解,下圖是ListView加載數(shù)據(jù)的工作原理(原理圖看不清楚的點(diǎn)擊后看大圖):
如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內(nèi)存(內(nèi)存內(nèi)存哦,說的優(yōu)化就是說在內(nèi)存中的優(yōu)化?。。。┲校渌脑赗ecycler中
ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的
當(dāng)item1滾出屏幕,并且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設(shè)定新的數(shù)據(jù)然后返回convertView,不必重新創(chuàng)建一個視圖
示例代碼如下
private MyCustomAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new MyCustomAdapter();
for (int i = 0; i < 50; i++) {
mAdapter.addItem("item " + i);
}
setListAdapter(mAdapter);
}
private class MyCustomAdapter extends BaseAdapter {
private ArrayList mData = new ArrayList();
private LayoutInflater mInflater;
public MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void addItem(final String item) {
mData.add(item);
notifyDataSetChanged();
}
@Override
public int getCount() {
return mData.size();
}
@Override
public String getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item1, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return convertView;
}
}
public static class ViewHolder {
public TextView textView;
}
}
執(zhí)行程序,查看日志:
getView 被調(diào)用 9 次 ,convertView 對于所有的可見項目是空值(如下):
然后稍微向下滾動List,直到item10出現(xiàn):
convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續(xù)滾動:
convertView不是空值了!item1離開屏幕到Recycler中去了,然后item11被創(chuàng)建,再滾動下:
此時的convertView非空了,在item11離開屏幕之后,它的視圖(…0f8)作為convertView容納item12了,好啦,結(jié)合以上原理,下面來看看今天最主要的話題,主角ListView的優(yōu)化:
首先,這個地方先記兩個ListView優(yōu)化的一個小點(diǎn):
1. ExpandableListView 與 ListActivity 由官方提供的,里面要使用到的ListView是已經(jīng)經(jīng)過優(yōu)化的ListView,如果大家的需求可以用Google自帶的ListView滿足的的話盡量用官方的,絕對沒錯!
2.其次,像小馬前面講的,說ListView優(yōu)化,其實并不是指其它的優(yōu)化,就是內(nèi)存是的優(yōu)化,提到內(nèi)存…(想到OOM,折騰了我不少時間),很多很多,先來寫下,如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item布局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠(yuǎn)遠(yuǎn)不夠的,如果數(shù)據(jù)過多,加載的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家,是警告……….也提醒下自己:
小馬碰到的問題大家應(yīng)該也都碰到過的,自定義的ListView項亂序問題,我很天真的在getView()中強(qiáng)制清除了下ListView的緩存數(shù)據(jù)convertView,也就是convertView = null了,雖然當(dāng)時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果數(shù)據(jù)太多的話,出現(xiàn)最最惡心的錯,手機(jī)卡死或強(qiáng)制關(guān)機(jī),關(guān)機(jī)啊哥哥們……O_O,客戶殺了我都有可能,但大家以后別犯這樣的錯了,單單使用清除緩存convertView是解決不了實際問題的,繼續(xù)……
下面是小記:圖片用完了正確的釋放…
ViewHolder Tag 必不可少,這個不多說!
如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內(nèi)存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
2.1:不要直接拿個路徑就去循環(huán)decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內(nèi)存去;
2.2: 拿到的圖片一定要經(jīng)過邊界壓縮
2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強(qiáng)引用。比如可以使 用WeakReference<Context> mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
2.4:在getView中做圖片轉(zhuǎn)換時,產(chǎn)生的中間變量一定及時釋放,用以下形式:
盡量避免在BaseAdapter中使用static 來定義全局靜態(tài)變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關(guān)鍵字,當(dāng)用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費(fèi)過多的實例(比如Context的情況最多),這時就要盡量避免使用了..
如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現(xiàn)內(nèi)存泄露的問題
盡量避免在ListView適配器中使用線程,因為線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控制
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。