Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 關於ListView的總結

關於ListView的總結

編輯:關於Android編程

關於ListView網上也有許多的介紹,我選了一些結合到了一起,僅供參考。

在android開發中ListView是比較常用的組件

官方API地址:http://www.android-doc.com/reference/android/widget/ListView.html

 

1.ListVeiw 用來展示列表的View。

2.適配器用來把數據映射到ListView上的中介。

3.數據具體的將被映射的字符串,圖片,或者基本組件。

根據列表的適配器類型,列表分為三種,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter

其中以ArrayAdapter最為簡單,只能展示一行字。SimpleAdapter有最好的擴充性,可以自定義出各種效果。SimpleCursorAdapter可以認為是SimpleAdapter對數據庫的簡單結合,可以方面的把數據庫的內容以列表的形式展示出來。

我們從最簡單的ListView開始:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /** * @author allin * */ publicclassMyListView extendsActivity { privateListView listView; //private List data = new ArrayList(); @Override publicvoidonCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); listView = newListView(this); listView.setAdapter(newArrayAdapter(this, android.R.layout.simple_expandable_list_item_1,getData())); setContentView(listView); } privateList getData(){ List data = newArrayList(); data.add("測試數據1"); data.add("測試數據2"); data.add("測試數據3"); data.add("測試數據4"); returndata; } }

上面代碼使用了windows/android-sdk-windows/docs/reference/android/widget/ArrayAdapter.html>)" target="_blank">ArrayAdapter(Contextcontext, int textViewResourceId,List objects)來裝配數據,要裝配這些數據就需要一個連接ListView視圖對象和數組數據的適配器來兩者的適配工作,ArrayAdapter的構造需要三個參數,依次為this,布局文件(注意這裡的布局文件描述的是列表的每一行的布局,android.R.layout.simple_list_item_1是系統定義好的布局文件只顯示一行文字,數據源(一個List集合)。同時用setAdapter()完成適配的最後工作。運行後的現實結構如下圖:

\

SimpleCursorAdapter

  sdk的解釋是這樣的:An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views。簡單的說就是方便把從游標得到的數據進行列表顯示,並可以把指定的列映射到對應的TextView中。

  下面的程序是從電話簿中把聯系人顯示到類表中。先在通訊錄中添加一個聯系人作為數據庫的數據。然後獲得一個指向數據庫的Cursor並且定義一個布局文件(當然也可以使用系統自帶的)。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /** * @author allin * */ publicclassMyListView2 extendsActivity { privateListView listView; //private List data = new ArrayList(); @Override publicvoidonCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); listView = newListView(this); Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null); startManagingCursor(cursor); ListAdapter listAdapter = newSimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1, cursor, newString[]{People.NAME}, newint[]{android.R.id.text1}); listView.setAdapter(listAdapter); setContentView(listView); } }

 Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);先獲得一個指向系統通訊錄數據庫的Cursor對象獲得數據來源。

 startManagingCursor(cursor);我們將獲得的Cursor對象交由Activity管理,這樣Cursor的生命周期和Activity便能夠自動同步,省去自己手動管理Cursor。

 SimpleCursorAdapter 構造函數前面3個參數和ArrayAdapter是一樣的,最後兩個參數:一個包含數據庫的列的String型數組,一個包含布局文件中對應組件id的int型數組。其作用是自動的將String型數組所表示的每一列數據映射到布局文件對應id的組件上。上面的代碼,將NAME列的數據一次映射到布局文件的id為text1的組件上。

注意:需要在AndroidManifest.xml中如權限:

運行後效果如下圖:

\

SimpleAdapter

simpleAdapter的擴展性最好,可以定義各種各樣的布局出來,可以放上ImageView(圖片),還可以放上Button(按鈕),CheckBox(復選框)等等。下面的代碼都直接繼承了ListActivity,ListActivity和普通的Activity沒有太大的差別,不同就是對顯示ListView做了許多優化,方面顯示而已。

下面的程序是實現一個帶有圖片的類表。

首先需要定義好一個用來顯示每一個列內容的xml

vlist.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 xmlversion="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageViewandroid:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px"/> <LinearLayoutandroid:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextViewandroid:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="22px" /> <TextViewandroid:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="13px" /> LinearLayout> LinearLayout>

下面是實現代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /** * @author allin * */ publicclassMyListView3 extendsListActivity { // private List data = new ArrayList(); @Override publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleAdapter adapter = newSimpleAdapter(this,getData(),R.layout.vlist, newString[]{"title","info","img"}, newint[]{R.id.title,R.id.info,R.id.img}); setListAdapter(adapter); } privateList> getData() { List> list = newArrayList>(); Map map = newHashMap(); map.put("title", "G1"); map.put("info", "google 1"); map.put("img", R.drawable.i1); list.add(map); map = newHashMap(); map.put("title", "G2"); map.put("info", "google 2"); map.put("img", R.drawable.i2); list.add(map); map = newHashMap(); map.put("title", "G3"); map.put("info", "google 3"); map.put("img", R.drawable.i3); list.add(map); returnlist; } }

使用simpleAdapter的數據用一般都是HashMap構成的List,list的每一節對應ListView的每一行。HashMap的每個鍵值數據映射到布局文件中對應id的組件上。因為系統沒有對應的布局文件可用,我們可以自己定義一個布局vlist.xml。下面做適配,new一個SimpleAdapter參數一次是:this,布局文件(vlist.xml),HashMap的 title 和 info,img。布局文件的組件id,title,info,img。布局文件的各組件分別映射到HashMap的各元素上,完成適配。

運行效果如下圖:

\

有按鈕的ListView

但是有時候,列表不光會用來做顯示用,我們同樣可以在在上面添加按鈕。添加按鈕首先要寫一個有按鈕的xml文件,然後自然會想到用上面的方法定義一個適配器,然後將數據映射到布局文件上。但是事實並非這樣,因為按鈕是無法映射的,即使你成功的用布局文件顯示出了按鈕也無法添加按鈕的響應,這時就要研究一下ListView是如何現實的了,而且必須要重寫一個類繼承BaseAdapter。下面的示例將顯示一個按鈕和一個圖片,兩行字如果單擊按鈕將刪除此按鈕的所在行。並告訴你ListView究竟是如何工作的。效果如下:

\

vlist2.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 xmlversion="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageViewandroid:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px"/> <LinearLayoutandroid:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextViewandroid:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="22px" /> <TextViewandroid:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="13px" /> LinearLayout> <Buttonandroid:id="@+id/view_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/s_view_btn" android:layout_gravity="bottom|right" /> LinearLayout>

程序代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 12 /** * @author allin * */ publicclassMyListView4 extendsListActivity { privateList> mData; @Override publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mData = getData(); MyAdapter adapter = newMyAdapter(this); setListAdapter(adapter); } privateList> getData() { List> list = newArrayList>(); Map map = newHashMap(); map.put("title", "G1"); map.put("info", "google 1"); map.put("img", R.drawable.i1); list.add(map); map = newHashMap(); map.put("title", "G2"); map.put("info", "google 2"); map.put("img", R.drawable.i2); list.add(map); map = newHashMap(); map.put("title", "G3"); map.put("info", "google 3"); map.put("img", R.drawable.i3); list.add(map); returnlist; } // ListView 中某項被選中後的邏輯 @Override protectedvoidonListItemClick(ListView l, View v, intposition, longid) { Log.v("MyListView4-click", (String)mData.get(position).get("title")); } /** * listview中點擊按鍵彈出對話框 */ publicvoidshowInfo(){ newAlertDialog.Builder(this) .setTitle("我的listview") .setMessage("介紹...") .setPositiveButton("確定", newDialogInterface.OnClickListener() { @Override publicvoidonClick(DialogInterface dialog, intwhich) { } }) .show(); } publicfinalclassViewHolder{ publicImageView img; publicTextView title; publicTextView info; publicButton viewBtn; } publicclassMyAdapter extendsBaseAdapter{ privateLayoutInflater mInflater; publicMyAdapter(Context context){ this.mInflater = LayoutInflater.from(context); } @Override publicintgetCount() { // TODO Auto-generated method stub returnmData.size(); } @Override publicObject getItem(intarg0) { // TODO Auto-generated method stub returnnull; } @Override publiclonggetItemId(intarg0) { // TODO Auto-generated method stub return0; } @Override publicView getView(intposition, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView == null) { holder=newViewHolder(); convertView = mInflater.inflate(R.layout.vlist2, null); holder.img = (ImageView)convertView.findViewById(R.id.img); holder.title = (TextView)convertView.findViewById(R.id.title); holder.info = (TextView)convertView.findViewById(R.id.info); holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn); convertView.setTag(holder); }else{ holder = (ViewHolder)convertView.getTag(); } holder.img.setBackgroundResource((Integer)mData.get(position).get("img")); holder.title.setText((String)mData.get(position).get("title")); holder.info.setText((String)mData.get(position).get("info")); holder.viewBtn.setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(View v) { showInfo(); } }); returnconvertView; } } }

  下面將對上述代碼,做詳細的解釋,listView在開始繪制的時候,系統首先調用getCount()函數,根據他的返回值得到listView的長度(這也是為什麼在開始的第一張圖特別的標出列表長度),然後根據這個長度,調用getView()逐一繪制每一行。如果你的getCount()返回值是0的話,列表將不顯示同樣return 1,就只顯示一行。

  系統顯示列表時,首先實例化一個適配器(這裡將實例化自定義的適配器)。當手動完成適配時,必須手動映射數據,這需要重寫getView()方法。系統在繪制列表的每一行的時候將調用此方法。getView()有三個參數,position表示將顯示的是第幾行,covertView是從布局文件中inflate來的布局。我們用LayoutInflater的方法將定義好的vlist2.xml文件提取成View實例用來顯示。然後將xml文件中的各個組件實例化(簡單的findViewById()方法)。這樣便可以將數據對應到各個組件上了。但是按鈕為了響應點擊事件,需要為它添加點擊監聽器,這樣就能捕獲點擊事件。至此一個自定義的listView就完成了,現在讓我們回過頭從新審視這個過程。系統要繪制ListView了,他首先獲得要繪制的這個列表的長度,然後開始繪制第一行,怎麼繪制呢?調用getView()函數。在這個函數裡面首先獲得一個View(實際上是一個ViewGroup),然後再實例並設置各個組件,顯示之。好了,繪制完這一行了。那再繪制下一行,直到繪完為止。在實際的運行過程中會發現listView的每一行沒有焦點了,這是因為Button搶奪了listView的焦點,只要布局文件中將Button設置為沒有焦點就OK了。

運行效果如下圖:

\

源碼下載


 

 

ListView的屬性

 

[java]view plaincopy   ListView去除滑動顏色。[html]view plaincopy android:cacheColorHint="@android:color/transparent"
[html]view plaincopy android:listSelector="@android:color/transparent"item 首先適應自己內容的大小,通過下面給以下兩個方法傳值改變寬高
[java]view plaincopy
  1. convertView.setMinimumWidth();
convertView.setMinimumHeight();

傳值只能大於item內容大小,小於不起作用

去除滾動條

 

[html]view plaincopy   android:scrollbars="none"
[java]view plaincopy android:fadingEdge="none"
[html]view plaincopy
  1. android:divider="@color/gray"
android:dividerHeight="1dp"
自定義完顏色後必須再設置高否則,將不顯示分割線

 

listItem 中包含ImageButton,Button,CheckBox等子控件會與 listView搶奪焦點
 

在根布局中添加

[html]view plaincopy   android:descendantFocusability="blocksDescendants"
[html]view plaincopy android:focusable="false"
第二是 transciptMode屬性,需要用ListView或者其它顯示大量Items的控件實時跟蹤或者查看信息,並且希望最新的條目可以自動滾動到可視范圍內。通過設置的控件transcriptMode屬性可以將Android平台的控件(支持ScrollBar)自動滑動到最底部。
[html]view plaincopy android:transcriptMode="alwaysScroll"
getView裡面優化視圖的幾種寫法
ViewCode
publicViewgetView(intposition,ViewconvertView,ViewGroupparent)
{
if(convertView==null)
{
convertView=LayoutInflater.from(context).inflate(R.layout.section_list_item1,null);
}
TextViewtv_name=(TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_name);
TextViewtv_phone=(TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_phoneNum);
ContactInfo1confo=contacts.get(position);
if(confo!=null){//toseteveryitem'stext
tv_name.setText(confo.getContactName());
tv_phone.setText(confo.getContact_Phone());
}
returnconvertView;
}

第二:
上面的寫法會有一個缺點,就是每次在getVIew的時候,都需要重新的findViewById,重新找到控件,然後進行控件的賦值以及事件相應設置。這樣其實在做重復的事情,因為的geiview中,其實包含有這些控件,而且這些控件的id還都是一樣的,也就是其實只要在view中findViewById一次,後面無需要每次都要findViewById了。
下面給出第二種寫法
寫發的特點,通常有一個內部類classViewHolder,這個ViewHolder,用來標識view中一些控件,方便進行一些事件相應操作的設置,比如onClick等等,這樣可以不用每次都要findViewById了,減少了性能的消耗。同時重用了convertView,很大程度上的減少了內存的消耗。
復制代碼代碼如下:
ViewCode
publicViewgetView(intposition,ViewconvertView,ViewGroupparent)
{
ViewHolderholder;
if(convertView==null){
convertView=LayoutInflater.from(context).inflate(R.layout.section_list_item1,null);
holder=newViewHolder();
holder.tv_name=(TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_name);
holder.tv_phone=(TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_phoneNum);
convertView.setTag(holder);
}
else
{
holder=(ViewHolder)convertView.getTag();
}
ContactInfo1confo=contacts.get(position);
Log.i("my","confo"+confo.getContactName());
if(confo!=null){//toseteveryitem'stext

holder.tv_name.setText(confo.getContactName());
holder.tv_phone.setText(confo.getContact_Phone());
}
returnconvertView;
}
classViewHolder
{
TextViewtv_name,tv_phone;
}

第三:
 個人覺得這個寫法是最舒服的,最舒服的意思是看著代碼有一種很爽,看的很清晰。
特點,使用了內部類classViewHolder、重用了convertView。
區別第二種寫法是,使用了一個臨時變量Viewview=convertView,然後修改view,最後返回view
復制代碼代碼如下:
ViewCode
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent)
{
Viewview=convertView;
ViewHolderholder;
if(view==null){
view=LayoutInflater.from(context).inflate(R.layout.section_list_item1,null);
holder=newViewHolder();
holder.tv_name=(TextView)view.findViewById(R.id.contact_contactinfoitem_tv_name);
holder.tv_phone=(TextView)view.findViewById(R.id.contact_contactinfoitem_tv_phoneNum);
view.setTag(holder);
}
else
{
holder=(ViewHolder)view.getTag();
}
ContactInfo1confo=contacts.get(position);
Log.i("my","confo"+confo.getContactName());
if(confo!=null){//toseteveryitem'stext

holder.tv_name.setText(confo.getContactName());
holder.tv_phone.setText(confo.getContact_Phone());
}
returnview;
}
classViewHolder
{
TextViewtv_name,tv_phone;
}

以上就是集中寫法,供新手學習和總結。
源代碼如下:LisViewTest.zip
根據樓下朋友提供的建議,發現還有優化的地方,最新更新如下:
復制代碼代碼如下:
ViewCode
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent)
{
Viewview=convertView;
ViewHolderholder;
if(view==null){
view=LayoutInflater.from(context).inflate(R.layout.section_list_item1,null);
holder=newViewHolder();
holder.tv_name=(TextView)view.findViewById(R.id.contact_contactinfoitem_tv_name);
holder.tv_phone=(TextView)view.findViewById(R.id.contact_contactinfoitem_tv_phoneNum);
view.setTag(holder);
}
else
{
holder=(ViewHolder)view.getTag();
}
ContactInfo1confo=contacts.get(position);
Log.i("my","confo"+confo.getContactName());
if(confo!=null){//toseteveryitem'stext

holder.tv_name.setText(confo.getContactName());
holder.tv_phone.setText(confo.getContact_Phone());
}
returnview;
}
staticclassViewHolder
{
TextViewtv_name,tv_phone;
}

注意:staticclassViewHolder
這裡設置ViewHolder為static,也就是靜態的,靜態類只會在第一次加載時會耗費比較長時間,但是後面就可以很好幫助加載,同時保證了內存中只有一個ViewHolder,節省了內存的開銷。
復制代碼代碼如下:
private void fixedListView(){
listView = (ListView) findViewById(R.id.listview);
ViewGroup.LayoutParams params = listView.getLayoutParams();
layout = (HorizontalScrollView) findViewById(R.id.layout);
params.height = layout.getHeight(); //需要設置的listview的高度,你可以設置成一個定值,也可以設置成其他容器的高度,如果是其他容器高度,那麼不要在oncreate中執行,需要做延時處理,否則高度為0
listView.setLayoutParams(params);
}

這樣發現ListView刷新的速度大幅加快。同事也解決了Item中文本輸入EditText中如果存在addTextChangedListener(new TextWatcher()的監聽,導致多次執行監聽代碼的情況。
復制代碼代碼如下:
xmlns:android="http://schemas.android.com/apk/res/android">




在改變button或者listview的item默認背景色,就可以用到selector。drawable可以設置為色彩資源,也可以設置為圖片資源。
1)設置listview的listSelector
復制代碼代碼如下:
android:id="@+id/history_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#565C5D"
android:dividerHeight="3dp"
android:listSelector="@drawable/selector"
android:cacheColorHint="@android:color/transparent">


2)在listitem的布局文件中設置background屬性,下面是listitem的布局文件
復制代碼代碼如下:

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/selector">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="歷史記錄"
android:textColor="#ffffff"
android:textSize="20sp"
android:layout_centerInParent="true">



3)在adapter的getView方法中設置
復制代碼代碼如下:
if(convertView ==null)
{
convertView = LayoutInflater.from(context).inflate(R.layout.listitem, null);
}
convertView.setBackgroundResource(R.drawable.selector);

  上述方法都能達到同樣的效果,就是改變item默認的背景色和點擊時的背景顏色,第三種方法最靈活,如果listview的奇數行和偶數行需要設置為不同的selector,只能用第三種方法。
2.包含button,checkbox等控件時點擊無響應問題。
  如果listitem裡面包括button或者checkbox等控件,默認情況下listitem會失去焦點,導致無法響應item的事件,最常用的解決辦法是在listitem的布局文件中設置descendantFocusability屬性。
復制代碼代碼如下:

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:descendantFocusability="blocksDescendants">

android:id="@+id/history_item_checkbt"
android:layout_height="30dp"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:checked="false"
>


android:id="@+id/history_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/history_item_checkbt"
android:background="@drawable/item_icon">



android:id="@+id/history_item_edit_bt"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="編輯"
android:textColor="#ffffff"
android:textSize="14sp"
android:background="@drawable/button_bg">


android:id="@+id/history_item_time_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="#565C5D"
android:textSize="14sp"
android:text="10-01 10:20"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@id/history_item_edit_bt">


android:id="@+id/history_item_title_tv"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_centerVertical="true"
android:textColor="#565C5D"
android:textSize="14sp"
android:text="xxxxxxxxXXXXXXXXXXXXXXXX"
android:ellipsize="end"
android:maxLines="1"
android:layout_toRightOf="@id/history_item_image"
android:layout_toLeftOf="@id/history_item_time_tv"
android:layout_marginLeft="3dp">


 

 



  1. android:layout_height="match_parent"
  2. android:gravity="center_horizontal"
  3. android:orientation="vertical">
  4.  
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:text="加載更多"/>
  8.  

 

此時我在調用listView.removeFootView(view);的時候出現了一個錯無,查找原因後終於明白

listView在set Adapter 類之前,一定要先listView.addFooterView(footerView);
[java]view plaincopy
  1. publicvoidsetAdapter(ListAdapteradapter){
  2. if(mHeaderViewInfos.size()>0||mFooterViewInfos.size()>0){
  3. mAdapter=newHeaderViewListAdapter(mHeaderViewInfos,mFooterViewInfos,adapter);
  4. }else{
  5. mAdapter=adapter;
  6. }
}[java]view plaincopy
  1. privateclassMyScrollListenerimplementsOnScrollListener{
  2.  
  3.  
  4. privatebooleanaddFlag;
  5.  
  6. @Override
  7. publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
  8.  
  9.  
  10. }
  11.  
  12. @Override
  13. publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
  14.  
  15. if(totalItemCount==visibleItemCount+firstVisibleItem&&firstVisibleItem!=0&&!addFlag){
  16. //TODO加載新的數據項
  17.  
  18. //如果有數據就addFlag=true;以便下次還能進入if語句進行加載
  19. listView.setSelection(totalItemCount-1);//讓listView顯示新加載項
  20.  
  21. }
  22. }
  23. }
根據實踐,數據從數據庫拉取,就不必添加Footview 因為速度很快,幾乎看不到就已經加載完成。 如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內存(內存內存哦,說的優化就是說在內存中的優化!!!)中,其他的在Recycler中
ListView先請求一個type1視圖(getView)然後請求其他可見的項目。convertView在getView中是空(null)的
當item1滾出屏幕,並且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數據然後返回convertView,不必重新創建一個視圖

下面來看下小馬從網上找來的示例代碼,網址搞丟了,只有一個word文檔,只能 copy過來,不然直接貼網址,結合上面的原理圖一起加深理解,如下:

  C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class MultipleItemsList extends ListActivity { 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; } }

 

執行程序,查看日志:

Android研究院之ListView原理學習與優化總結(二十一) - 雨松MOMO程序研究院 - 2

getView 被調用 9 次 ,convertView 對於所有的可見項目是空值(如下):

Android研究院之ListView原理學習與優化總結(二十一) - 雨松MOMO程序研究院 - 3

然後稍微向下滾動List,直到item10出現:

Android研究院之ListView原理學習與優化總結(二十一) - 雨松MOMO程序研究院 - 4

 

convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續滾動:

Android研究院之ListView原理學習與優化總結(二十一) - 雨松MOMO程序研究院 - 5

 

convertView不是空值了!item1離開屏幕到Recycler中去了,然後item11被創建,再滾動下:

Android研究院之ListView原理學習與優化總結(二十一) - 雨松MOMO程序研究院 - 6

 

此時的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是解決不了實際問題的,繼續……
下面是小記:圖片用完了正確的釋放…

  C# 1 2 3 4 if(!bmp.isRecycle() ){ bmp.recycle() //回收圖片所占的內存 system.gc()//提醒系統及時回收 }

下面來列舉下真正意義上的優化吧:

  1. ViewHolder Tag 必不可少,這個不多說!
  2. 如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
    2.1:不要直接拿個路徑就去循環decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內存去;
    2.2: 拿到的圖片一定要經過邊界壓縮
    2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使 用WeakReference mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
    2.4:在getView中做圖片轉換時,產生的中間變量一定及時釋放,用以下形式:
  3. 盡量避免在BaseAdapter中使用static 來定義全局靜態變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了..
  4. 如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現內存洩露的問題
  5. 盡量避免在ListView適配器中使用線程,因為線程產生內存洩露的主要原因在於線程生命周期的不可控制
記下小馬自己的錯誤:
之前使用的自定義ListView中適配數據時使用AsyncTask自行開啟線程的,這個比用Thread更危險,因為Thread只有在run函數不 結束時才出現這種內存洩露問題,然而AsyncTask內部的實現機制是運用了線程執行池(ThreadPoolExcutor,要想了解這個類的話大家加下我們的Android開發群五號,因為其它群的存儲空間快滿了,所以只上傳到五群裡了,看下小馬上傳的Gallery源碼,你會對線程執行池、軟、弱、強引用有個更深入的認識),這個類產生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現內存洩露的問題。這個問題的解決辦法小馬當時網上查到了記在txt裡了,如下:
6.1:將線程的內部類,改為靜態內部類。
6.2:在線程內部采用弱引用保存Context引用
示例代碼如下:

 

  C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public abstract class WeakAsyncTask extends AsyncTask { protected WeakReference mTarget; public WeakAsyncTask(WeakTarget target) { mTarget = new WeakReference(target); } /** {@inheritDoc} */ @Override protected final void onPreExecute() { final WeakTarget target = mTarget.get(); if (target != null) { this.onPreExecute(target); } } /** {@inheritDoc} */ @Override protected final Result doInBackground(Params... params) { final WeakTarget target = mTarget.get(); if (target != null) { return this.doInBackground(target, params); } else { return null; } } /** {@inheritDoc} */ @Override protected final void onPostExecute(Result result) { final WeakTarget target = mTarget.get(); if (target != null) { this.onPostExecute(target, result); } } protected void onPreExecute(WeakTarget target) { // No default action } protected abstract Result doInBackground(WeakTarget target, Params... params); protected void onPostExecute(WeakTarget target, Result result) { // No default action } }

 

其實在ListView適配器的getView()方法中可以做很多的優化,我記得還有可以優化findViewById()這個方法來尋址資源信息效率的方法。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved