Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android上用模板方法模式實現具有自動重用View功能的Adapter

Android上用模板方法模式實現具有自動重用View功能的Adapter

編輯:關於Android編程

ListView和GridView簡介


在Android App的開發中, ListView和GridView等控件是使用非常頻繁的控件。 這兩個控件的特點是使用數據適配器來顯示數據, 並且在數據項較多的時候, 可以重用用於顯示數據的條目(這裡的條目指的是ListView或GridView中用於顯示每一個數據項的子View)。本文的重點並不是講解ListView和GridView的內部實現或使用方法,但是後文講解的自定義Adapter是被ListView和GridView所調用的,所以在這裡簡單提及ListView和GridView。ListView,GridView和Adapter的關系如下圖所示:

\



<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPGgxPsrKxeTG97XE0ruw48q508O3vcq9PC9oMT4KPGJyPgoK08nT2kdyaWRWaWV3tcTKudPDt73KvbrNTGlzdFZpZXe63M/gJiMyMDI4NDujrCDOqsHL0PDK9rXEvPK94KOsIM/CzsTWu9LUTGlzdFZpZXfOqsD9vbK94qGj1Nq/qrei1tCjrExpc3RWaWV3us1BZGFwdGVytcTKudPDt73KvdK7sOPH6b/2z8LKx9Xi0fm1xKO6Cjxicj4KCjEgvMyz0FNES9bQtcRCYXNlQWRhcHRlcqOsIMq1z9a6zdK1zvHP4LnYtcTX1Lao0uVBZGFwdGVyCjxicj4KCjIgtLS9qNK7uPZBZGFwdGVytcTKtcD9o6wgtffTw0xpc3RWaWV3tcRzZXRBZGFwdGVyt723qKOsIL2r1eK49kFkYXB0ZXK21M/zo6wgyejWw7W9TGlzdFZpZXe21M/ztcTE2rK/oaMKPGJyPgoK1NrV4sDv1vfSqr2yyvbKtc/W19S2qNLlQWRhcHRlcrXEuf2zzKGjINTavMyz0EJhc2VBZGFwdGVyuvOjrCDSqsq1z9ZCYXNlQWRhcHRlctbQtqjS5bXEs+nP87e9t6hnZXRWaWV3oaNnZXRWaWV3t723qMrH08lMaXN0Vmlld7bUz/O199PDtcShoyC4xcCo0rvPwqOsIL7NysdMaXN0Vmlld7bUz/PU2tDo0qrSu7j20MK1xMz1xL9WaWV3tcTKsbryo6wgu+G199PD1eK49mdldFZpZXe3vbeoo6zE47HY0OvKtc/W1eK49re9t6ijrLe1u9jSu7j217yxuLrDtcRWaWV3oaO1q8rHo6xMaXN0Vmlld77f09DW2NPDzPXEv1ZpZXe1xLmmxNyhowog0rK+zcrHsrvKx8O/tM7Q6NKq0ru49sz1xL9WaWV3try00823tLS9qKGjy/nS1GdldFZpZXe3vbeo09DSu7j2ss7K/WNvbnZlcnRWaWV3o6wg1eK49tKyysfTyUxpc3RWaWV3tKu1vUFkYXB0ZXLW0LXEoaMgtbFMaXN0Vmlld9bQtObU2sTcubvW2NPDtcRWaWV3yrG+zb2r1eK49lZpZXe0q7W9Z2V0Vmlld7e9t6jW0KGjIGdldFZpZXe3vbeotcTKtc/W1d/SqsXQts9jb252ZXJ0Vmlld8rHt/HOqr/Vo6wgyOe5+7K7zqq/1aOsIL7N1tjTw9XiuPZWaWV3oaMKPGJyPgoK19S2qNLlQWRhcHRlctbQtcRnZXRWaWV3yrXP1re9yr3Su7DjyOfPwqO6o6jV4sDvzqrBy73ayqHKsbzko6zWsb3TyrnTw8/uxL/W0LXEtPrC66OsILbB1d/Wu9DoudjXomdldFZpZXcgtcTKtc/W0M7KvaOsILK708O52NDE0rXO8c/gudi1xLHkwb/D+7PGo6kKPGJyPgoKPHByZSBjbGFzcz0="brush:java;"> private static class ScheduleAdapter extends BaseAdapter{ private List meetings ; private Context context; private LayoutInflater inflator; public ScheduleAdapter(Context context ) { this.context = context; inflator = LayoutInflater.from(context); } public ScheduleAdapter(Context context, List meetings) { super(); this.context = context; this.meetings = meetings; inflator = LayoutInflater.from(context); } public List getMeetings() { return meetings; } public void setMeetings(List meetings) { this.meetings = meetings; } @Override public int getCount() { return meetings.size(); } @Override public Object getItem(int position) { return meetings.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = null; if(convertView == null){ v = inflator.inflate(R.layout.schedule_item, null); ViewHolder holder = new ViewHolder(); holder.name = (TextView) v.findViewById(R.id.schedule_name_id); holder.time = (TextView) v.findViewById(R.id.schedule_time_id); holder.icon = (ImageView) v.findViewById(R.id.schedule_icon_id); v.setTag(holder); }else{ v = convertView; } ViewHolder holder = (ViewHolder) v.getTag(); Schedule data = meetings.get(position); holder.name.setText(data.getName()); holder.time.setText(data.getStartTime() + " " + data.getEndTime()); return v; } private static class ViewHolder{ public TextView name; public TextView time; public ImageView icon; } }

從上述代碼可以看出, getView方法中處理的邏輯有兩個:

1 判斷是否可以重用View, 如果不能重用, 就創建新的條目View, 找到條目View中的各個子View, 被存放在一個ViewHolder獨享中。如果可以重用View, 就重用這個view, 並且從和這個View相關的ViewHolder對象中找到已經緩存的子View。


2 將具體的數據和事件綁定到各個子View上。



實現自動重用View的Adapter


從上面的代碼經常被使用到, 每次都要在getView方法中寫這些重用View的邏輯, 並且還要每次都寫一個業務相關的ViewHolder類,真的是非常非常惡心。那麼有沒有辦法封裝一下這些惡心的邏輯,而不用每次都從頭寫起呢? 本文提供了一種實現方法。
首先給出完整的實現代碼, 然後對代碼中的關鍵點進行分析。
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * 一個具有自動重用View功能的適配器實現
 * 
 * @author zhangjg
 * @date Feb 20, 2014 10:57:42 AM
 */
public abstract  class AutoReuseViewAdapter extends BaseAdapter {

	//Item布局資源
	private int itemLayoutRes;
	
	//Item中要操作的各個子View的Id
	private int[] childViewIds;

	//布局填充器
	private LayoutInflater inflator;

	
	public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
		
		inflator = LayoutInflater.from(context);

		this.itemLayoutRes = itemLayoutRes;
		this.childViewIds = childViewIds;

	}


	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		
		View v = null;
		
		ViewHolder holder;
		
		
		if(convertView != null){   
			v = convertView;      
			holder = (ViewHolder) v.getTag();
		}else{
			v = inflator.inflate(itemLayoutRes, null);
			
			holder = new ViewHolder();
			
			for(int childId : childViewIds ){
				holder.cacheView(v.findViewById(childId));
			}
			
			v.setTag(holder);
		}
		
		boundDataAndEventToViews(position, v, holder.getCachedViews());
		
		return v;
	}

	/**
	 * 抽象方法, 被子類覆蓋, 將業務相關的數據和事件綁定到特定的子View上
	 */
	protected abstract void boundDataAndEventToViews(int position,
			View itemView, ArrayList childViews);

	
	/**
	 * 內部類, 用於緩存條目View的子控件, 同樣是面向抽象和業務無關的
	 * 
	 * @author zhangjg
	 * @date Feb 20, 2014 9:12:33 AM
	 */
	private static class ViewHolder{
		private ArrayList cachedViews = new ArrayList();
		
		public void cacheView(View v){
			cachedViews.add(v);
		}
		
		public ArrayList getCachedViews(){
			return cachedViews;
		}
	}

}

首先解釋一下這個抽象類的成員變量。 itemLayoutRes是條目View的布局資源, childViewIds是一個int型的數組, 用於存放條目View中要處理的各個子View的id 。 這兩個成員變量是通過構造方法注入的, 代碼如下:
	private int itemLayoutRes;
	
	//Item中要操作的各個子View的Id
	private int[] childViewIds;

	//布局填充器
	private LayoutInflater inflator;

	
	public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
		
		inflator = LayoutInflater.from(context);

		this.itemLayoutRes = itemLayoutRes;
		this.childViewIds = childViewIds;

	}

在構造方法中, 還要根據context創建一個LayoutInflater對象, 用於從XML布局中加載條目View。
然後說一下getView方法的實現。 這個類中, 將getView中的邏輯分到兩個方法中去處理。首先重用View的邏輯是業務無關的, 在getView 中實現, 將數據和事件綁定到各個子View上是業務相關的, 交給boundDataAndEventToViews方法處理, 這是個抽象方法, 必須由子類覆蓋並實現, 因為業務相關的東西也只有具體子類才知道。
現在再從整體上審視一下這個Adapter的實現。 由於getView是由ListView調用的, 至於如何調用, 何時調用我們無法干預,但是不管怎麼寫代碼, getView方法必須完成屬於他的所有任務。 這裡再重復一下getView 的職責:

1 判斷是否可以重用View, 如果不能重用, 就創建新的條目View, 找到條目View中的各個子View, 被存放在一個ViewHolder獨享中。如果可以重用View, 就重用這個view, 並且從和這個View相關的ViewHolder對象中找到已經緩存的子View。


2 將具體的數據和事件綁定到各個子View上。


在我們實現的這個AutoReuseViewAdapter中, getView方法同樣完成了兩個職責。 也就是說原有的處理流程還是由getView方法定義, 只是將部分邏輯(綁定數據和事件到子View)交給具體子類去實現, 這是標准的模板方法設計模式。


使用自動重用View功能的Adaper


在上面的步驟中, AutoReuseViewAdapter已經實現好了。 現在就看看如何根據這個AutoReuseViewAdapter實現自己業務相關的Adapter。基於AutoReuseViewAdapter實現自己的Adapter, 只需要做以下工作:
1 自定義自己的Adapter, 繼承AutoReuseViewAdapter;
2 在子類的構造方法中調用父類AutoReuseViewAdapter的構造方法, 將條目布局的資源索引和條目View中各個子View 的Id傳到父類構造器中;
3 覆蓋並實現父類中的抽象方法, 在boundDataAndEventToViews方法中對子View進行數據和事件綁定。
在上面“適配器的一般使用方式“一節中, 給出了適配器的一般實現。 現在我們基於AutoReuseViewAdapter重新實現它, 看看是不是簡單了很多。下面是重新實現的版本:
	private static class ScheduleAdapter extends /*BaseAdapter*/AutoReuseViewAdapter{

		private List meetings ;
		
		public ScheduleAdapter(Context context ) {
			super(context, R.layout.schedule_item, 
					R.id.schedule_name_id, R.id.schedule_time_id, R.id.schedule_icon_id);
		}

		public ScheduleAdapter(Context context, List meetings) {
			this(context);
			this.meetings = meetings;
			
		}

		public List getMeetings() {
			return meetings;
		}

		public void setMeetings(List meetings) {
			this.meetings = meetings;
		}

		@Override
		public int getCount() {
			return meetings.size();
		}

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

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


		@Override
		protected void boundDataAndEventToViews(int position, View itemView,
				ArrayList childViews) {
			
			Schedule data = meetings.get(position);
			
			((TextView)childViews.get(0)).setText(data.getName());
			((TextView)childViews.get(1)).setText(data.getStartTime() + "		" + data.getEndTime());
			//((ImageView)childViews.get(2)).setImageResource(resId);
		}
		
	}

看, 是不是變得簡單多了。 我們現在只需要關心業務相關的邏輯了, 不用再關心業務無關的邏輯(重用View的邏輯)。所以, 如果在項目中有一些業務無關的代碼總是重復的寫, 那麼有很大可能有優化的余地, 這時我們就要發揚程序員的”懶惰“的優良傳統,將重復的邏輯抽取出來。
最後有兩點需要強調:
1 因為父類中的getView方法定義了完整的邏輯流程, 所以子類絕對不要覆蓋getView方法, 只要實現boundDataAndEventToViews方法就夠了。
2 boundDataAndEventToViews方法中, 需要綁定數據和事件的子View是由一個集合ArrayList childViews, 通過父類傳遞到子類中的, 子View在集合中的順序, 和構造方法中子View的Id傳入的順序相同。 以上面的代碼為例,在構造方法中子View的id傳入的順序如下: R.id.schedule_name_id, R.id.schedule_time_id, R.id.schedule_icon_id 。 第一個是顯示名字的TextView, 第二個是顯示時間的TextView, 第三個是顯示圖標的ImageView。所以在boundDataAndEventToViews中childViews集合中子View的順序也是這樣的。這種順序相關性必須由子類定義和維護, 如果順序不對, 可能會拋出類型轉化異常。




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