Adapter基類抽象(一)

--清晰概念

--示例

--初步分析

--對比

--設計和實現

--知識點和總結


清晰概念

先來說說用的非常多的BaseAdapter,這是官方提供給我們的一個適配器基類,通過他我們可以自定義列表的項,實現各種列表。說到適配器這幾個字,咱們先來分析下適配器的定義和作用,之前說到軟件抽象於現實,現實當中我們身邊充滿着很多適配器作用的東西,比如一個兩項插頭需要插進三項插頭,我們怎麼辦,我們可以找個轉換器,這樣就可以插進去了,這個轉換器解決了形狀不適用的問題。還比如電源適配器,解決了變壓的問題,而且很多電源適配器上標寫的既是Adapter(適配器),這種思想是可以用在很多地方的。再來說我們軟件當中的適配器模式,Android中定義的就是Adapter,很直接,它實現了數據到列表視圖的轉換,這裏能看出來了我們需要哪些東西來實現列表展示數據,數據+適配器+列表組件,組件官方已經提供給我們了,他們也把適配器定義好了,數據我們需要自己定義和獲取,我們只需要按照他給出的接口實現即可。

一張圖從形上來說明適配器思想(當然不要想歪了。。。):



示例

文章中我以實現、設計、分析反序的方式來講可能效率會更高一些,這也是我的學習方法:實踐觀察然後再學習,學習過程效率會更高,容易理解。

關於適配器的封裝優化先看下下面兩個適配器實際使用對比


首先實體類:

public class User {
/**
  *  很多文章在性能優化中提到,實體的get、set可以捨棄,直接public即可。
  */
        public String name;
        public String sex;
        public User(String name, String sex) {
            this.name = name;
            this.sex = sex;
        }
    }

使用BaseAdapter開發示例代碼:

public class TestListActivity extends Activity{
ListView lv;
    TestAdapter adapter;
    ArrayList<User> data=new ArrayList<User>();
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
        loadData();
    }
private void initView(){
        lv=new ListView(this);
        lv.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
        lv.setDrawingCacheBackgroundColor(Color.TRANSPARENT);
        setContentView(lv);
        adapter=new TestAdapter();
        lv.setAdapter(adapter);
    }
private void loadData(){
        data.add(new User("小京","男"));
        data.add(new User("大京","女"));
        data.add(new User("京京","不詳"));
        adapter.notifyDataSetChanged();
    }
class TestAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return data.size();
        }
        @Override
        public Object getItem(int position) {
            return data.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder=null;
            if(null==convertView){
                convertView= LayoutInflater.from(TestListActivity.this).inflate(R.layout.activity_list_test_item,null);
                viewHolder=new ViewHolder();
                viewHolder.tvName= (TextView) convertView.findViewById(R.id.tv_name);
                viewHolder.tvSex= (TextView) convertView.findViewById(R.id.tv_sex);
                convertView.setTag(viewHolder);
            }else{
                viewHolder= (ViewHolder) convertView.getTag();
            }
            User user=data.get(position);
            viewHolder.tvName.setText(user.name);
            viewHolder.tvSex.setText(user.sex);
            return convertView;
        }
        class ViewHolder{
            TextView tvName;
            TextView tvSex;
        }
    }
}

運行效果:



封裝改造後,使用BasicAdapter使用代碼:

public class OptimizedTestListActivity extends Activity{
ListView lv;
    OptimizedTestAdapter adapter;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
        loadData();
    }
private void initView(){
        lv=new ListView(this);
        lv.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
        lv.setDrawingCacheBackgroundColor(Color.TRANSPARENT);
        setContentView(lv);
        adapter=new OptimizedTestAdapter(this);
        lv.setAdapter(adapter);
    }
private void loadData(){
        ArrayList<User> data=new ArrayList<User>();
        data.add(new User("小京","男"));
        data.add(new User("大京","女"));
        data.add(new User("京京","不詳"));
        adapter.addDataAndNotifyDataSetChanged(data);
    }
class OptimizedTestAdapter extends BasicAdapter<User> {
        public OptimizedTestAdapter(Context mctx) {
            super(mctx);
        }
        @Override
        protected int setItemLayout() {
            return R.layout.activity_list_test_item;
        }
        @Override
        protected void bindData(View convertView, User data, int position) {
            TextView tvName=get(convertView,R.id.tv_name);
            TextView tvSex=get(convertView,R.id.tv_sex);
            tvName.setText(data.name);
            tvSex.setText(data.sex);
        }
    }
}


初步分析

之前有提到,提取封裝的時候我們需要找出“變”和“不變”的部分,不變的部分抽取出來,變得部分是我們提供出來的接口可供實現不同效果,關於分層抽象,我們根據不同的需要來抽象到什麼程度取決於你定義的“不變部分”,有興趣的可以看看比如SimpleAdapter的實現。

不變的部分:

1.數據容器聲明

2.繼承BaseAdapter後需要實現的方法

3.其中getView接口爲了防止列表項混亂,我們還要引入ViewHolder

4.getView中部分執行過程

變的部分:

1.容器內放置的數據類型

2.列表項佈局

3.數據與視圖的映射關係


:這裏我們把getView部分重新定義了,官方的定義是:列表組件根據適配器的getView可以獲取到我們自定義的列表項View,來實現渲染。

大部分情況getView中的寫法和執行過程是固定的,不變的只是數據和組件映射關係以及其他組件上的功能,所以我們重新定義一下叫bindData。


注意看上面優化後的適配器的使用,只需要實現變得部分即可,重複的部分已經不需要再寫了。


對比

優點:不變的部分已經不需要重複編寫了

1.數據容器,把數據容器直接定義在適配器中,操作數據的時直接使用適配器中聲明的操作數據的接口即可

2.BaseAdapter中的抽象函數不需要再重複實現,如果有特殊需要你也可以override

3.通過調用get函數直接獲取組件,已經實現了viewholder機制並且也不需要強制類型轉換。

到這裏,基本就實現了適配器的改造,把重複的部分都省去。關於get中實現的ViewHolder機制之前在博客看到的,具體忘記出處了,作者請諒解,這裏邏輯是一樣的,技巧在SparseArray的使用和泛型的使用去掉了強制類型轉換。

4.變得部分也基本都能滿足需求,對於數據展示,組件交互等等這些都是可以實現的。

 

設計和實現

下面來介紹如何實現這個BasicAdapter,是怎樣的一個思考過程,這是比較有價值的地方。

完整的BasicAdapter的源代碼:

public abstract class BasicAdapter<T> extends BaseAdapter {

	/**上下文*/
	protected Context mctx;
	
	/**數據源*/
	protected List<T> datas;//全量數據

	/**佈局ID*/
	protected int layoutId;

	public BasicAdapter(Context mctx) {
		this.mctx = mctx;
		//初始化數據集合的引用
		this.datas=new ArrayList<T>();
		//初始化佈局資源的id,通過抽象函數限制使用者必須傳遞必要參數
		layoutId=setItemLayout();
	}
	
	protected abstract int setItemLayout();

    public Context getContext(){
        return mctx;
    }

	public List<T> getDatas(){return datas;}

	public boolean isEmpty(){
		return datas.isEmpty();
	}

	@Override
	public int getCount() {
			return null != datas && !datas.isEmpty() ? datas.size() : 0;
	}

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

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		T t = datas.get(position);
		if (convertView == null) {
			convertView = LayoutInflater.from(mctx).inflate(layoutId, null);
		}
		//在這裏我們調用抽象函數bindData,使用時不同的部分就在這個函數中,這是回調思想的使用
		bindData(convertView, t,position);
		//防止使用時沒有使用get函數獲取組件,我們可以加入異常機制
		if(null==convertView.getTag())throw new NullPointerException("bindData中請調用ViewHolder的get靜態方法去實例化組件");
		return convertView;
	}

	/**
	 * 子類實現,自定義組件和數據之間的映射關係(必須用ViewHolder.get靜態方法獲取)
	 * @param convertView
	 * @param data
     * @param position
	 */
	protected abstract void bindData(View convertView, T data,int position);

	public void refreshDataAndNotifyDataSetChanged(List<? extends T> newDatas){
		if(null==newDatas||newDatas.isEmpty())return;
		datas.clear();
		datas.addAll(newDatas);
		notifyDataSetChanged();
	}
	
	public void addDataAndNotifyDataSetChanged(List<? extends T> newDatas){
		if(null==newDatas||newDatas.isEmpty())return;
        datas.addAll(newDatas);
        notifyDataSetChanged();
    }

    public void addDataAndNotifyDataSetChanged(T newDatas){
    	if(null==newDatas)return;
        datas.add(newDatas);
        notifyDataSetChanged();
    }

    public BasicAdapter clearDatas(){
    	datas.clear();
		return this;
    }

    @SuppressWarnings("unchecked")
    public <V extends View> V get(View convertView, int id) {
    	/*
    	 * 關於SparseArray的應用:可以去百度查閱資料學習。
    	 * 對於HashMap<Integer,Object>的使用,Android官方建議是使用SparseArray<E>的。
    	 * 參考:http://www.eoeandroid.com/thread-321547-1-1.html
    	 */
//    	Exception.isNull(convertView, "請檢查getView中的convertView對象是否初始化");
    	
        SparseArray<View> viewHolder = (SparseArray<View>) convertView.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray<View>();
            convertView.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = convertView.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (V) childView;
    }
}


知識點和總結

1.泛型基本應用

2.抽象類抽象函數

封裝好實現,給定約束也就是必須實現的接口

3.這裏用到了框架開發思想,還有設計模式中的模板模式

框架中大量的抽象會用到模板模式,本質就是提取相同的運行機制,然後開放接口實現不同處。記住,實際運行時,是框架在調用你的代碼。


前面分析到找出“變”和“不變”的部分,當我們不需要用到其他類型的時候,我們基於BaseAdapter擴展一層即可,這就是在針對BaseAdapter進行細分定義和改造,我們具象了我們一般用不到的接口,然後改造了getView處的邏輯和概念,因爲我們找出的“變“的本質是”數據與組件之間的映射“,所以我們需要把映射以外重複的事情提取出來,提取出來的執行過程是不變的,這也就是模板模式的思想,抽象過程需要的必要參數我們可以以各種方式實現,比如本例中的佈局id,我們可以像文中抽象函數方式要求使用者必須實現傳遞進來,或者傳參定義在構造函數中。Android應用開發的過程,肯定會有大量的可複用部分,比如列表界面或者詳情界面,根據不同的定義,可以抽取出很多層可複用的模板類,針對不同的變化程度來選擇使用,這會大大提高開發效率、減少錯誤率、方便擴展和維護。建議大家平時就抓住機會完善自己的“工具”,用的時候纔會得心應手,工具比如面向對象思想、設計模式、抽象思維、邏輯思維、反射技術、一些好的設計思想、現成的框架模型等等等。

 

下面我會接着本文再寫幾篇對於Adapter進一步擴展的文章,來更快速更好的實現更多需求。哪裏講的不對的直接聯繫我,看到錯誤並指出的人,我想說咱們做朋友~


by LuoJ


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章