android之優雅書寫多類型Adapter

寫了這麼多年的adapter,一直以來都是在搬磚,按部就班,從沒想過如何將adapter寫的更加優雅,這個實在是不應該,內心着實慚愧,直到看了recyclerView書寫的優雅adapter才恍然大悟,以下是原作者的博客地址:

http://www.jianshu.com/p/1297d2e4d27a?winzoom=1

由於自己項目還是listview,所以參考了作者寫的recycleview,改動了一番。寫下此博客一個是對可訪問者模式的理解,一個是練習面向接口編程,將代碼解耦。這篇文章簡單,但是涉及東西還是值得一學

囉嗦一句,設計模式看的再多如果沒有實際東西操作,真的很難去掌握,也很難深入骨髓,這也是作者本人親身感受,所以藉此機會能夠用上一番,對自己也有莫大的好處

涉及的知識點:

   1、可訪問者模式:這是設計模式中的一種,若還不瞭解自行百度,也比較簡單
  2、簡單的泛型
  
基本這些就夠了。接下來我們直接看代碼
MutilTypeAdapter:

按照以前的寫法,我相信很多人都是一下這樣的或者根據model的type逐一分類
@Override
	public int getItemViewType(int position) {
		// TODO Auto-generated method stub
		Object obj = mDataList.get(position);
		if (obj instanceof AdInfo) {
			
			return 0;
		} else if (obj instanceof UserListInfo) {
			
			return 1;
		} else if (obj instanceof TopicListInfo) {
			
			return 2;
		} else if (obj instanceof BangListInfo) {
			
			return 3;
		} else if (obj instanceof .GroupListInfo) {
						return 4;
		} else if (obj instanceof .TagListInfo) {
			
			return 5;
		} else if (obj instanceof VideoListInfo) {
			
			return 6;
		} else if (obj instanceof .KnowledgeListInfo) {
			
			return 7;
		}
		return super.getItemViewType(position);
	}



我相信絕大多人都寫過類似的爛代碼,這種陋習在沒學過訪問者模式或者沒熟用這個模式之前很容易就寫出來,而且絕大多數人都覺得這是理所應當的,可能還自我感覺良好,,接下來看看經過改良過的方法:


 @Override
    public int getItemViewType(int position) {
        return modelList.get(position).type(typeFactory);
    }

一行代碼就可以搞定,這是多麼神奇又令人興奮的事兒,這個不但是一行代碼的問題,它的可擴展性絕對比你之前的寫法要好上好多,也符合開閉原則“對外可擴展,又不用修改原有代碼”,何樂而不爲呢。接下來針對demo講解訪問者模式中的結構對象與訪問者
結構對象:
    在這裏的結構對象就是數據源(item的model),所以我們這裏定義個接口,所有類型的model都要實現它,因爲訪問者訪問model後獲取與其相對應的type值然後根據type值創建相應的holder與view:

public interface Visitable {
    int type(TypeVisitorFactory typeFactory);
}
當所有model都實現這個接口後,他們就具有結構對象的能力,而且只要是實現這個接口就一定有這個能力,所以這又可以從複雜業務中解耦。那麼我們再來看看訪問者
訪問者:TypeVisitoryFactory
public interface TypeVisitorFactory {
    int type(One one);

    int type(Two two);

    int type(Three three);

    int type(Normal normal);

    BaseViewHolder createViewHolder(int type,Context context);
}

這一看也是個接口,而且重載了很多type()方法,這個是幹什麼用的呢?我們來看看它的一個實現:
public class TypeVisitorFactoryImp implements TypeVisitorFactory {
    private static final String TAG = "TypeVisitorFactoryImp";
    private final int TYPE_ONE = 0;//type 值必須從0開始  詳解http://www.cnblogs.com/RGogoing/p/5872217.html
    private final int TYPE_TWO = 1;
    private final int TYPE_THREE = 2;
    private final int TYPE_NORMAL = 3;
    @Override
    public int type(One one) {
        Log.d(TAG, "TYPE_ONE = " + TYPE_ONE);
        return TYPE_ONE;
    }

    @Override
    public int type(Two one) {
        Log.d(TAG,"TYPE_TWO = "+TYPE_TWO);
        return TYPE_TWO;
    }

    @Override
    public int type(Three one) {
        Log.d(TAG,"TYPE_THREE = "+TYPE_THREE);
        return TYPE_THREE;
    }

    @Override
    public int type(Normal normal) {
        Log.d(TAG,"TYPE_NORMAL = "+TYPE_NORMAL);
        return TYPE_NORMAL;
    }

    @Override
    public BaseViewHolder createViewHolder(int type,Context context) {
        if (TYPE_ONE == type) {
            return new OneViewHolder(context);
        } else if (TYPE_TWO == type) {
            return new TwoViewHolder(context);
        } else if (TYPE_THREE == type) {
            return new ThreeViewHolder(context);
        } else if (TYPE_NORMAL == type) {
            return new NormalViewHolder(context);
        }
        return null;
    }
}

這一看type()方法返回的都是類型,而且參數都是各個類型的model,也許你猜出來了,由於之前都是通過instanceof 來返回type值,但是現在只要通過實現相應的type(model),就可以返回相應的type值。這是怎麼做到的呢。

在回頭捋一遍:model實現了Visitable接口具有結構對象的能力,只要model傳入相應的訪問者(TypeVisitoryFactory),訪問者調用type(model),即可得到type。由於java的高級用法多態,使我們在調用方法時至多父類接口,它會自行調用子類的實現。所以只要adapter的數據源是一個visitable就很輕而易舉的實現了現在的方式

結構對象和訪問者講完了,接下來看看adapter的數據源於getView是怎樣工作的
數據源:
 private List<Visitable> modelList = new ArrayList<Visitable>();
數據源果然是一個Visitable,那你是不是有個疑問,數據源是一個接口形式,那麼每次獲取model的時候是不是都要強轉,而且還要根據類型去強轉,一來二去的好麻煩的樣子。其實不然,細心的你可能發現上邊訪問者的接口和實現時發現一個熟悉的方法,bingo,就是BaseViewHolder createViewHolder(type);想當然的是,根據type值返回一個holview對象。那麼接下來我們看看BaseViewHolder的定義:
public abstract class BaseViewHolder<T> {
    private SparseArray<View> views;
    private View mItemView;

    public BaseViewHolder(Context context, int resId) {
        views = new SparseArray<>();
        this.mItemView = View.inflate(context, resId, null);
        mItemView.setTag(this);
    }
    public View getmItemView() {
        return mItemView;
    }

    public abstract void setUpView(T model, int position, MultiTypeAdapter adapter);
}
這是一個抽象類,而且還是一個帶泛型的抽象類,有一個抽象方法一定引起了你的注意:
setUpView(T model xxxx);
相信聰明的你已經猜出來了,T model就是我們各個類型對應的實際model,每種model對應一個holder類。所以這裏要對泛型的知識有一些基礎性的瞭解就很容易明白了。
這裏只貼出我項目中優化過的holder實例:
public abstract class BaseViewHolder<T> {

    private View mItemView;
    protected Context context;

    public BaseViewHolder(Context context, int resId) {
        this.context = context;
        this.mItemView = View.inflate(context, resId, null);
        mItemView.setTag(this);
        initView(mItemView);
    }

    public View getmItemView() {
        return mItemView;
    }

    /**更新view的數據*/
    public abstract void updateViewData(T model, int position, SearchResultAllAdapter adapter);

    /***
     * findViewById
     *
     * @param mItemView
     */
    abstract void initView(View mItemView);

}

這裏不需要將所有findviewByid的view緩存到SparseArray中,直接按照以前的adapter的holder方式就行,只要findviewById一次就好:只要在各個實現類中實現initView,初始化一次就好了:
public class ArticleViewHolder extends BaseViewHolder<One > {
    private  TextView tvArticleTitle;
    private  TextView tvArticleContent;
    private  TextView tvArticleFrom;
    public ArticleViewHolder(Context context) {
        super(context,R.layout.lmb_all_search_article_item);
    }

    @Override
    public void updateViewData(One model, int position, AllAdapter adapter) {
        tvArticleFrom.setText(model.bname);
        final String articleId = model.id;
        getmItemView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               
    }

    @Override
    void initView(View mItemView) {
        tvArticleTitle = (TextView) mItemView.findViewById(R.id.tv_search_article_title);
        tvArticleContent = (TextView) mItemView.findViewById(R.id.tv_search_article_content);
        tvArticleFrom = (TextView) mItemView.findViewById(R.id.tv_search_article_from);
    }


}



這樣是不是很明瞭了,每個類型對應一個Holer,所有操作互不關心,只要是實現holder的相應方法就可以了;擴展性都好;


那麼我們看看getView是怎麼工作的:

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // 帖子信息
        final BaseViewHolder holder;
        if (convertView == null) {
            Log.d(TAG, "convertView == null = " + position);
            holder = typeFactory.createViewHolder(getItemViewType(position), mContext);
            convertView = holder.getmItemView();
        } else {
            Log.d(TAG, "convertView != null = " + position);
            holder = (BaseViewHolder) convertView.getTag();
        }
        holder.setUpView(modelList.get(position), position, this);
        return convertView;
    }

holder = typeFactory.createViewHolder(getItemViewType(position), mContext);

主要是這句:直接通過getItemViewType,創建相應的holder,然後直接調用holder.setUpView();直接將接口model傳遞過去,由於是泛型所以解決了上述說的強轉的問題;
這麼一看這樣的getview即簡潔又明瞭,而且後續增加新的item都不用動其中的代碼即可完成。只需要添加新的HolderView、新增類型的model且model實現Visitable 最後重載訪問者的 type(model)方法就完成了。



這裏需要注意的地方:
    listview的adapter中的getItemviewType必須是以0開始,多一個類型自增1,相應的getItemViewCount必須是item種類的個數,即有幾個type其count=type個數
可以查閱這篇文章

這裏提一點不相關的可能又用得上的建議有些模塊需要互相通信的,比如各個fragment相互通信的,可以通過中介者模式完全解耦,第三方框架evenBus用起來也是解耦但是就是太亂了太雜了,搞不好都不好管理,所以有時自己以中介者模式或變形來做會更好

這樣我們就講完了。對以上不清楚的,可下載demo自行琢磨






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