Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android-----帶你一步一步優化ListView(一)

android-----帶你一步一步優化ListView(一)

編輯:關於Android編程

ListView作為android中最常使用的控件,可以以條目的形式顯示大量的數據,經常被用於顯示最近聯系人列表,對於每一個 Item,均要求adapter的getView方法返回一個View,因此ListView的實現是離不開Adapter的,如果以MVC的思想來看ListView的話,ListView的顯示相當於V,Adapter部分相當於C,而數據部分就相當於M了,接下來的幾篇博客計劃對ListView自己所了解的一些優化措施總結一下,希望能夠幫助到大家;

先來看看如果我們不使用任何優化措施的話,使用ListView的方法:

這裡先補充下獲得LayoutInflater的三種方法:

(1)如果是在Activity中的話,可以調用

LayoutInflater inflater = getLayoutInflater();

(2)如果不是在Activity中,則可以將context上下文作為參數,通過

context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

(3)如果不是在Activity中,則可以將context上下文作為參數,通過

LayoutInflater inflater = LayoutInflater.from(context);

首先定義Activity界面布局listview.xml,很簡單,裡面就只有一個ListView

 

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
    <listview android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent">
</listview></linearlayout>
再定義ListView的每個item的布局item.xml,也很簡單,只有一個TextView

 

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> 
    <textview android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="50dp">
</textview></linearlayout>

接下來定義一個Adapter,繼承自BaseAdapter,用來為ListView填充數據

 

public class ListViewAdapter extends BaseAdapter{

	List list = new ArrayList();
	LayoutInflater inflater = null;
	
	public ListViewAdapter(List list,Context context) {
		this.list = list;
		inflater = LayoutInflater.from(context);
	}
	@Override
	public int getCount() {
		return list.size();
	}

	@Override
	public Object getItem(int position) {
		return list.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = null;
		System.out.println("before: "+list.get(position)+"-------  "+convertView);
		view = inflater.inflate(R.layout.item, null);
	    System.out.println("after: "+list.get(position)+"-------  "+view);
		TextView textView = (TextView) view.findViewById(R.id.textView);
		textView.setText(list.get(position));
		return view;
	}
}

 

 

可以發現,我們在getView裡並沒有做任何的優化,待會我們看看這樣的方式會出什麼問題;

 

定義Activity,將Adapter綁定到ListView上面

 

public class MainActivity extends Activity {

	List list = new ArrayList();
	ListView listView = null;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.listview);
		listView = (ListView) findViewById(R.id.listView);
		for(int i = 1;i < 50;i++)
		{
			list.add("item "+i);
		}
		ListViewAdapter adapter = new ListViewAdapter(list, this);
		listView.setAdapter(adapter);
	}
}
很簡單,我們模擬了有50個條目的ListView,並且通過ListViewAdapter的構造函數將其傳遞給Adapter用於在界面展示這些數據;

 

運行效果圖:

\

 

對應的Logcat輸出:

\

可以發現初始狀態有10個item顯示在界面上,並且他們的before view均是null的,調用inflate方法之後生成了新的view,因此after view非空了;

接著我們向上拖動屏幕,可以在Logcat看到有如下輸出:

\

可以發現此時的item 19和item 20在before之前convertView得地址都是@4181a590,這個view是已經劃出屏幕的item 9的,這點可以從剛開始第一屏幕的輸出看出來,因為item 9已經被加入到了RecycleBin緩存中了,所以在調用getView方法的時候convertView獲取到的是緩存中的隨機一個view,item 19和item 20完全有可能獲取到同一個view,但是他們的after view是不可能出現相同的,也就是說我們這個例子中的after view的地址值是不可能相同的,因為我們模擬了50個item,所以會有50個view被放到RecycleBin緩存中,也許現在對於我們顯示來說沒什麼,那麼如果有大量的信息需要顯示的話,直接就會報內存的;

通過上面我們會發現一點,在調用getView方法的時候,他的第二個參數可能不會是null的,原因就是RecycleBin緩存幫我們暫存了那些劃出屏幕的view,所以我們在convertView非空的情況下我們也沒什麼必要重新調用inflate方法加載布局了,因為這個方法畢竟也是要解析xml文件的,至少是要花時間的,直接使用從緩存中取出的view即可啦,先來看看ListView提供的RecycleBin緩存圖解:

\

從上面的圖上可以看到當item 1被劃出屏幕之後,會被放到Recycle緩存中,當item 8要劃入屏幕的時候,如果他和item 1的類型相同的話,則直接從Recycle中獲得即可,即此時的convertView不再是null;如果他和item 1類型不一致的話,則會新建view視圖,即此時convertView等於null;

好的,那我們接下來就該充分使用android提供給我們的RecycleBin機制來優化ListView了;

只需要修改ListViewAdapter類的getView方法即可了:

 

@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = null;
		System.out.println("before: "+list.get(position)+"-------  "+convertView);
		if(convertView == null)
		{
			view = inflater.inflate(R.layout.item, null);
		}else
			view = convertView;
		TextView textView = (TextView) view.findViewById(R.id.textView);
		textView.setText(list.get(position));
		return view;
	}
不滑動屏幕的時候,程序輸出:

 

\

接著我們滑動屏幕,查看輸出:

\

注意紅色部分,發現沒經過11個item,都會復用之前的view,這也就是說我們的RecycleBin緩存中將只有11個view了,不像前面那樣有50個view,這在很大程度上節約了內存,想想如果有上千萬條數據需要顯示,每個數據條目都有一個view在RecycleBin中是一件多麼可怕的事情,進行了convertView是否為null的判斷之後,將只會緩存一屏幕的view,當然有可能會多那麼幾個吧;

上面我們通過判斷convertView是否為空對ListView進行了優化,接下來我們看看getView方法,裡面在獲取TextView的時候,我們使用了findViewById方法,這個方法是與IO有關的操作,想必也會影響性能吧,他只要的目的是獲得某一個view的布局罷了,我們如果有了view的話,其實只需要第一次將該view和其布局綁定到一起就可以了,沒必要每次都為view設置布局了,這也就是使用setTag的目的了;

這裡也僅僅只是對ListViewAdapter的geyView方法進行修改即可:

 

@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = null;
		ViewHolder viewHolder = null;
		System.out.println("before: "+list.get(position)+"-------  "+convertView);
		if(convertView == null)
		{
			view = inflater.inflate(R.layout.item, null);
			viewHolder = new ViewHolder();
			viewHolder.textView = (TextView) view.findViewById(R.id.textView);
			view.setTag(viewHolder);
		}else
		{
			view = convertView;
			viewHolder = (ViewHolder) view.getTag();
		}
		viewHolder.textView.setText("item "+list.get(position));
		return view;
	}
	static class ViewHolder
	{
		TextView textView;
	}
這裡采用靜態內部類的方式用於定義item 的各個控件 ,如果convertView非空的話,表示該view對應的布局已經存在了,只需要調用getTag獲取到即可了,如果convertView為空的話,則需要通過findViewById來獲取這個布局中的控件,並且最後將該布局通過setTag設置到view上面即可;

 

程序的輸出結果和上面是一樣的,這裡不再列出;

我們平常的實際應用中,每個條目有可能不都是一樣的,這種情況下會出現不同的項目布局,那麼這時候該怎麼辦呢?同樣我們通過實例來學習一下這時候的RecycleBin機制是怎麼實現緩存的;

在此,我們增加一個只顯示一個按鈕的條目,布局文件button.xml

 


修改ListViewAdapter類如下:

 

 

public class ListViewAdapter extends BaseAdapter{

	List list = new ArrayList();
	LayoutInflater inflater = null;
	
	public ListViewAdapter(List list,Context context) {
		this.list = list;
		inflater = LayoutInflater.from(context);
	}
	@Override
	public int getItemViewType(int position) {
		//0表示顯示的是TextView,1表示顯示的是Button
		if(position % 4 != 0)
			return 0;
		else 
			return 1;
	}
	@Override
	public int getViewTypeCount() {
		//表示有兩種類型的item布局
		return 2;
	}
	@Override
	public int getCount() {
		return list.size();
	}

	@Override
	public Object getItem(int position) {
		return list.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = null;
		TextViewHolder textViewHolder = null;
		ButtonViewHolder buttonViewHolder = null;
		System.out.println("before: "+list.get(position)+"-------  "+convertView);
		int type = getItemViewType(position);
		if(convertView == null)
		{
			switch (type) {
			case 0:
				view = inflater.inflate(R.layout.item, null);
				textViewHolder = new TextViewHolder();
				textViewHolder.textView = (TextView) view.findViewById(R.id.textView);
				view.setTag(textViewHolder);
				textViewHolder.textView.setText(list.get(position));
				break;
			case 1:
				view = inflater.inflate(R.layout.button, null);
				buttonViewHolder = new ButtonViewHolder();
				buttonViewHolder.button = (Button) view.findViewById(R.id.button);
				view.setTag(buttonViewHolder);
				buttonViewHolder.button.setText("button "+list.get(position));
				break;
			default:
				break;
			}
		}else
		{
			view = convertView;
			switch (type) {
			case 0:
				textViewHolder = (TextViewHolder) view.getTag();
				textViewHolder.textView.setText(list.get(position));
				break;
			case 1:
				buttonViewHolder = (ButtonViewHolder) view.getTag();
				buttonViewHolder.button.setText("button "+list.get(position));
				break;
			default:
				break;
			}
		}
		return view;
	}
	static class TextViewHolder
	{
		TextView textView;
	}
	static class ButtonViewHolder
	{
		Button button;
	}
}
通過getViewTypeCount()返回的是你到底有多少種布局,我們這裡有兩種

 

通過getItemViewType(int)返回的是根據你的position得到的對應布局的ID,當然這個ID是可以由你來定的;

修改Activity類

 

public class MainActivity extends Activity {

	List list = new ArrayList();
	ListView listView = null;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.listview);
		listView = (ListView) findViewById(R.id.listView);
		for(int i = 1;i < 50;i++)
		{
			if(i % 4 == 0)
			{
				list.add("button "+i);
			}else
				list.add("item "+i);
		}
		ListViewAdapter adapter = new ListViewAdapter(list, this);
		listView.setAdapter(adapter);
	}
}
界面運行效果圖為:

 

\

可以發現每隔3個條目我們都會加載另一個不同的條目,接著查看Logcat輸出:

\

在我們滑動屏幕之後,輸出結果為:

\

注意圖中突出顯示部分,可以發現對於button item,我們也得到了復用,當然這個復用順序並不一定是按順序來的,因為RecycleBin機制只會把你已經滑出屏幕的item緩存下來,但是在從緩存中取得時候,並不一定就是按你存進去的順序取出來的,這點要注意啦,到此一般的ListView優化測試結束了,我們來總結一下:

(1)通過復用view的方式來充分利用android系統本身自帶的RecycleBin緩存機制,能夠保證即使有再多的item實際中也僅僅會有有限多個item,大大節省內存;

(2)使用靜態內部類以及setTag方式,將view與其對應的控件綁定起來,避免了每次得到view以後都需要通過findViewById的方式來獲取控件;

(3)對於有多種布局的ListView來說,我們可以通過getViewTypeCount()獲得布局的種類,通過getItemViewType(int)獲得當前位置上的布局到底是屬於哪一類;

好了,這篇先介紹到這裡

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