android ListView詳解

列表的顯示須要三個元素:

  1. ListVeiw:  用來展現列表的View。html

  2. 適配器 : 用來把數據映射到ListView上java

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

           根據列表的適配器類型,列表分爲三種,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,這三種適配器的使用你們可學習下官網上面的使用或者自行百度谷歌,一堆DEMO!!!其中以ArrayAdapter最爲簡單,只能展現一行字。SimpleAdapter有最好的擴充性,能夠自定義出各類效果。SimpleCursorAdapter能夠認爲是SimpleAdapter對數據庫的簡單結合,能夠方便的把數據庫的內容以列表的形式展現出來。web

           系統要繪製ListView了,他首先用getCount()函數獲得要繪製的這個列表的長度,而後開始繪製第一行,怎麼繪製呢?調用getView()函數。在這個函數裏面首先得到一個View(這個看實際狀況,若是是一個簡單的顯示則是View,若是是一個自定義的裏面包含不少控件的時候它實際上是一個ViewGroup),而後再實例化並設置各個組件及其數據內容並顯示它。好了,繪製完這一行了。那 再繪製下一行,直到繪完爲止,前面這些東西作下鋪墊,繼續…….數據庫

 

           如今咱們再來了解ListView加載數據的原理,有了這方面的瞭解後再說優化才行,下面先跟你們一塊兒來看下ListView加載數據的基本原理小馬就直接寫了:數組

ListView的工做原理以下:

                 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加載數據的工做原理(原理圖看不清楚的點擊後看大圖):緩存

 

 


下面簡單說下上圖的原理:

  1. 若是你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內存(內存內存哦,說的優化就是說在內存中的優化!!!)中,其餘的在Recycler中app

  2. ListView先請求一個type1視圖(getView)而後請求其餘可見的項目。convertView在getView中是空(null)的ide

  3. 當item1滾出屏幕,而且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數據而後返回convertView,沒必要從新建立一個視圖函數

 咱們從最簡單的ListView開始:

public   class   MyListView extends   Activity {
 
     private   ListView listView;
     //private List<String> data = new ArrayList<String>();
     @Override
     public   void   onCreate(Bundle savedInstanceState){
         super .onCreate(savedInstanceState);
         
         listView = new   ListView( this );
         listView.setAdapter( new   ArrayAdapter<String>( this , android.R.layout.simple_expandable_list_item_1,getData()));
         setContentView(listView);
     }
     
     
     
     private   List<String> getData(){
         
         List<String> data = new   ArrayList<String>();
         data.add( "測試數據1" );
         data.add( "測試數據2" );
         data.add( "測試數據3" );
         data.add( "測試數據4" );
         
         return   data;
     }
}
上面代碼使用了ArrayAdapter(Context context, int textViewResourceId, List<T> objects)來裝配數據,要裝配這些數據就須要一個鏈接ListView視圖對象和數組數據的適配器來二者的適配工做,ArrayAdapter的構造須要三個參數,依次爲this,佈局文件(注意這裏的佈局文件描述的是列表的每一行的佈局,android.R.layout.simple_list_item_1是系統定義好的佈局文件只顯示一行文字,數據源(一個List集合)。同時用setAdapter()完成適配的最後工做。運行後的現實結構以下圖:
 
 

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而且定義一個佈局文件(固然也可使用系統自帶的)。

public      class      MyListView2   extends      Activity {
 
       private      ListView listView;
       //private List<String> data = new ArrayList<String>();
       @Override
       public      void      onCreate(Bundle savedInstanceState){
           super   .onCreate(savedInstanceState);
         
           listView =   new      ListView(   this   );
         
           Cursor cursor = getContentResolver().query(People.CONTENT_URI,   null   ,   null   ,   null   ,   null   );
           startManagingCursor(cursor);
         
           ListAdapter listAdapter =   new      SimpleCursorAdapter(   this   , android.R.layout.simple_expandable_list_item_1,
                   cursor,
                   new      String[]{People.NAME},
                   new      int   []{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中如權限:<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>

 

SimpleAdapter

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

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

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

vlist.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<ImageView android:id="@+id/img" 
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" 
		android:layout_margin="5px"/>
	<LinearLayout android:orientation="vertical"
		android:layout_width="wrap_content" 
		android:layout_height="wrap_content">
		<TextView android:id="@+id/title" 
			android:layout_width="wrap_content"
			android:layout_height="wrap_content" 
			android:textColor="#FFFFFFFF"
			android:textSize="22px" />
		<TextView android:id="@+id/info" 
			android:layout_width="wrap_content"
			android:layout_height="wrap_content" 
			android:textColor="#FFFFFFFF"
			android:textSize="13px" />
	</LinearLayout>
</LinearLayout>

下面是實現代碼:

/**
 * @author allin
 * 
 */
public class MyListView3 extends ListActivity {
	// private List<String> data = new ArrayList<String>();
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.vlist,
				new String[]{"title","info","img"},
				new int[]{R.id.title,R.id.info,R.id.img});
		setListAdapter(adapter);
	}
	private List<Map<String, Object>> getData() {
		List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("title", "G1");
		map.put("info", "google 1");
		map.put("img", R.drawable.i1);
		list.add(map);
		map = new HashMap<String, Object>();
		map.put("title", "G2");
		map.put("info", "google 2");
		map.put("img", R.drawable.i2);
		list.add(map);
		map = new HashMap<String, Object>();
		map.put("title", "G3");
		map.put("info", "google 3");
		map.put("img", R.drawable.i3);
		list.add(map);
		
		return list;
	}
}

使用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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal" 
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">


	<ImageView android:id="@+id/img" 
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" 
		android:layout_margin="5px"/>

	<LinearLayout android:orientation="vertical"
		android:layout_width="wrap_content" 
		android:layout_height="wrap_content">

		<TextView android:id="@+id/title" 
			android:layout_width="wrap_content"
			android:layout_height="wrap_content" 
			android:textColor="#FFFFFFFF"
			android:textSize="22px" />
		<TextView android:id="@+id/info" 
			android:layout_width="wrap_content"
			android:layout_height="wrap_content" 
			android:textColor="#FFFFFFFF"
			android:textSize="13px" />

	</LinearLayout>


	<Button android: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>

程序代碼:

/**
 * @author allin
 * 
 */
public class MyListView4 extends ListActivity {


	private List<Map<String, Object>> mData;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mData = getData();
		MyAdapter adapter = new MyAdapter(this);
		setListAdapter(adapter);
	}

	private List<Map<String, Object>> getData() {
		List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("title", "G1");
		map.put("info", "google 1");
		map.put("img", R.drawable.i1);
		list.add(map);

		map = new HashMap<String, Object>();
		map.put("title", "G2");
		map.put("info", "google 2");
		map.put("img", R.drawable.i2);
		list.add(map);

		map = new HashMap<String, Object>();
		map.put("title", "G3");
		map.put("info", "google 3");
		map.put("img", R.drawable.i3);
		list.add(map);
		
		return list;
	}
	
	// ListView 中某項被選中後的邏輯
	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		
		Log.v("MyListView4-click", (String)mData.get(position).get("title"));
	}
	
	/**
	 * listview中點擊按鍵彈出對話框
	 */
	public void showInfo(){
		new AlertDialog.Builder(this)
		.setTitle("個人listview")
		.setMessage("介紹...")
		.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
			}
		})
		.show();
		
	}
	
	
	
	public final class ViewHolder{
		public ImageView img;
		public TextView title;
		public TextView info;
		public Button viewBtn;
	}
	
	
	public class MyAdapter extends BaseAdapter{

		private LayoutInflater mInflater;
		
		
		public MyAdapter(Context context){
			this.mInflater = LayoutInflater.from(context);
		}
		@Override
		public int getCount() {
			// TODO Auto-generated method stub
			return mData.size();
		}

		@Override
		public Object getItem(int arg0) {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public long getItemId(int arg0) {
			// TODO Auto-generated method stub
			return 0;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			
			ViewHolder holder = null;
			if (convertView == null) {
				
				holder=new ViewHolder();  
				
				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(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					showInfo();					
				}
			});
			
			
			return convertView;
		}
		
	}
	
	
	
	
}

  下面將對上述代碼,作詳細的解釋,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了。

相關文章
相關標籤/搜索