您好,登錄后才能下訂單哦!
首先,讓我們來了解下系統時怎么繪制listview的:
ListView繪制的過程如下:
首先,系統在繪制ListView之前,將會先調用getCount方法來獲取Item的個數。
之后每繪制一個Item就會調用一次getView方法,在此方法(getView)內就可以引用事先定義好的xml來確定顯示的效果并返回一個View對象作為一個Item顯示出來。也正是在這個過程中完成了適配器的主要轉換功能,把數據和資源以開發者想要的效果顯示出來。也正是getView的重復調用,使得ListView的使用更為簡單和靈活。這兩個方法是自定ListView顯示效果中最為重要的,同時只要重寫好了就兩個方法,ListView就能完全按開發者的要求顯示。而getItem和getItemId方法將會在調用ListView的響應方法的時候被調用到。所以要保證ListView的各個方法有效的話,這兩個方法也得重寫。比如:沒有完成getItemId方法的功能實現的話,當調用ListView的getItemIdAtPosition方法時將會得不到想要的結果,因為該方法就是調用了對應的適配器的getItemId方法。
Adapter :
Adapter是連接后端數據和前端顯示的適配器接口,是數據和UI(View)之間一個重要的紐帶。在常見的View(ListView,GridView)等地方都需要用到Adapter。如下圖直觀的表達了Data、Adapter、View三者的關系:
Adapter的類有好幾個,直接看圖:
Android ListView理解,BaseAdapter
ListView是Android開發過程中較為常見的組件之一,它將數據以列表的形式展現出來。一般而言,一個ListView由以下三個元素組成:
1.View,用于展示列表,通常是一個xml所指定的。大家都知道Android的界面基本上是由xml文件負責完成的,所以ListView的界面也理所應當的使用了xml定義。例如在ListView中經常用到的“android.R.layout.simple_list_item_1”等,就是Android系統內部定義好的一個xml文件。
2.適配器,用來將不同的數據映射到View上。不同的數據對應不同的適配器,如ArrayAdapter,CursorAdapter,SimpleAdapter等,他們能夠將數組,指針指向的數據,Map等數據映射到View上。也正是由于適配器的存在,使得ListView的使用相當靈活,經過適配器的處理后,在view看來所有的數據映射過來都是一樣的。
3.數據,具體的別映射的數據和資源,可以是字符串,圖片等,通過適配器,這些數據將會被現實到ListView上。所有的數據和資源要顯示到ListView上都通過適配器來完成。
系統已有的適配器可以將基本的數據顯示到ListView上,如:數組,Cursor指向的數據,Map里的數據。但是在實際開發中這些系統已實現的適配器,有時不能滿足我們的需求。而且系統自帶的含有多選功能ListView在實際使用過程中會有一些問題。要實現復雜的ListView可以通過繼承ListView并重寫相應的方法完成,同時也可以通過繼承BaseAdapter來實現。通過文檔可以看出,ArrayAdapter,CursorAdapter,SimpleAdapter都繼承于BaseAdapter。所以通過繼承BaseAdapter就可以完成自己的Adapter,可以將任何復雜組合的數據和資源,以任何你想要的顯示效果展示給大家。
繼承BaseAdapter之后,需要重寫以下四個方法:getCount,getItem,getItemId,getView。
在getView中可以完成自己想要的界面布局
具體的實現方法如下
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的條目,實現數據的初始化,為自己的控件設置監聽事件
* @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);
//保存視圖狀態
contentview.setTag(holder);
} else {
holder = (ViewHolder) contentview.getTag();
this.setItemInfo( holder, position, mDataBase);
this.setClickListener(holder, position, mDataBase);
}
return contentview;
}
/**
* 設置一個容器用于存放控件
*/
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在開始繪制的時候,系統首先調用getCount()函數,根據他的返回值得到ListView的長度,然后根據這個長度,調用getView()一行一行的繪制ListView的每一項。如果你的getCount()返回值是0的話,列表一行都不會顯示,如果返回1,就只顯示一行。返回幾則顯示幾行。如果我們有幾千幾萬甚至更多的item要顯示怎么辦?為每個Item創建一個新的View?不可能!!!實際上Android早已經緩存了這些視圖,大家可以看下下面這個截圖來理解下,這個圖是解釋ListView工作原理的最經典的圖了大家可以收藏下,不懂的時候拿來看看,加深理解,其實Android中有個叫做Recycler的構件,順帶列舉下與Recycler相關的已經由Google做過N多優化過的東東比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不難理解,下圖是ListView加載數據的工作原理(原理圖看不清楚的點擊后看大圖):
如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內存(內存內存哦,說的優化就是說在內存中的優化!!!)中,其他的在Recycler中
ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的
當item1滾出屏幕,并且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數據然后返回convertView,不必重新創建一個視圖
示例代碼如下
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;
}
}
執行程序,查看日志:
getView 被調用 9 次 ,convertView 對于所有的可見項目是空值(如下):
然后稍微向下滾動List,直到item10出現:
convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續滾動:
convertView不是空值了!item1離開屏幕到Recycler中去了,然后item11被創建,再滾動下:
此時的convertView非空了,在item11離開屏幕之后,它的視圖(…0f8)作為convertView容納item12了,好啦,結合以上原理,下面來看看今天最主要的話題,主角ListView的優化:
首先,這個地方先記兩個ListView優化的一個小點:
1. ExpandableListView 與 ListActivity 由官方提供的,里面要使用到的ListView是已經經過優化的ListView,如果大家的需求可以用Google自帶的ListView滿足的的話盡量用官方的,絕對沒錯!
2.其次,像小馬前面講的,說ListView優化,其實并不是指其它的優化,就是內存是的優化,提到內存…(想到OOM,折騰了我不少時間),很多很多,先來寫下,如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item布局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠遠不夠的,如果數據過多,加載的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家,是警告……….也提醒下自己:
小馬碰到的問題大家應該也都碰到過的,自定義的ListView項亂序問題,我很天真的在getView()中強制清除了下ListView的緩存數據convertView,也就是convertView = null了,雖然當時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果數據太多的話,出現最最惡心的錯,手機卡死或強制關機,關機啊哥哥們……O_O,客戶殺了我都有可能,但大家以后別犯這樣的錯了,單單使用清除緩存convertView是解決不了實際問題的,繼續……
下面是小記:圖片用完了正確的釋放…
ViewHolder Tag 必不可少,這個不多說!
如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
2.1:不要直接拿個路徑就去循環decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內存去;
2.2: 拿到的圖片一定要經過邊界壓縮
2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使 用WeakReference<Context> mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
2.4:在getView中做圖片轉換時,產生的中間變量一定及時釋放,用以下形式:
盡量避免在BaseAdapter中使用static 來定義全局靜態變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關鍵字,當用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了..
如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現內存泄露的問題
盡量避免在ListView適配器中使用線程,因為線程產生內存泄露的主要原因在于線程生命周期的不可控制
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。