項(xiàng)目地址:https://github.com/razerdp/FriendCircle
一起擼個(gè)朋友圈吧這是本文所處文集,所有更新都會(huì)在這個(gè)文集里面哦,歡迎關(guān)注
上篇鏈接:http://www.itdecent.cn/p/dc5782a494b5
下篇鏈接:http://www.itdecent.cn/p/1f85d3978bb5
文章開始之前,談?wù)凧SON這個(gè)數(shù)據(jù)合集的問題吧,關(guān)于JSON,因?yàn)樵谙略诠緦?shí)習(xí)的時(shí)候?qū)懥撕枚啻谓馕觯f實(shí)話,在發(fā)現(xiàn)GsonFormat插件這個(gè)東東之前,,,我寫JSON解析都是這么寫的。。:
xxx=json.optXXX("xxx",xxx);
唉,現(xiàn)在真的挺佩服當(dāng)時(shí)的耐心,面對(duì)這么多的JSON Array,Object我居然有如此耐心一個(gè)一個(gè)去手動(dòng)解析。
這個(gè)一起擼朋友圈文章寫到這里,其實(shí)我也發(fā)現(xiàn)了,似乎沒有后端支持,還真挺難搞的,同時(shí)因?yàn)槲业漠厴I(yè)設(shè)計(jì)也需要一些后端的支持,所以在下決定兩者同時(shí)并行。。。希望有一天,可以擼一個(gè)簡(jiǎn)單的服務(wù)器來支撐我們這個(gè)項(xiàng)目-V-
廢話完了,進(jìn)入今天的主題吧
首先感謝同事的思想(這是他的git哦:https://github.com/wenjiahui ),這篇文章擴(kuò)展于他的idea.
關(guān)于一個(gè)Adapter,我們應(yīng)該也寫過很多很多了,無非就是繼承一個(gè)BaseAdapter,實(shí)現(xiàn)那些getXXX,然后getView里面用viewholder裝起來。
確實(shí),我們的朋友圈adapter也是基于這個(gè)思想,但是略有改變。
其一,我們有多個(gè)type,比如圖文,文字什么的,我們需要區(qū)分這些type。這個(gè)好辦,ItemViewType解決,嗯這沒問題。
其二,我們有各種各樣的ClickEvent,比如點(diǎn)擊頭像,點(diǎn)擊圖片,長(zhǎng)按文字復(fù)制,點(diǎn)擊評(píng)論,點(diǎn)擊名字什么的。這個(gè)好辦,不同的clickListener,嗯,這么問題。
那么,現(xiàn)在問題來了,如果我們?cè)赼dapter真按照平時(shí)的寫法來實(shí)現(xiàn)上面兩點(diǎn),那么我們將會(huì)看到這樣的代碼:
...getItem什么的此處略過
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder1 xxx;//圖文viewholder
ViewHolder2 xxx;//文字viewholder
ViewHolder3 xxx;//網(wǎng)頁viewholder
...好多viewholder
...好多viewholder初始化
switch(getItemViewType(position)){
case xxx:
...好多代碼;
...xxx.setOnClickListener(xxx);//點(diǎn)擊xxx的listener,下面也許還有n個(gè)
break;
case xxx:
...好多代碼;
...xxx.setOnClickListener(xxx);//點(diǎn)擊xxx的listener,下面也許還有n個(gè)
break;
...還好好多case.....
}
class ViewHolder 1{
...
}
class ViewHolder 2{
...
}
class ViewHolder 3{
...
}
OnClickListener xxx1=new OnClickListener{ onClick(View v) }
OnClickListener xxx2=new OnClickListener{ onClick(View v) }
OnClickListener xxx3=new OnClickListener{ onClick(View v) }
...
}
我相信,沒有幾個(gè)人愿意看到一個(gè)adapter一千多行或者兩千行代碼吧。。。其中還混有N個(gè)viewholder和N個(gè)點(diǎn)擊事件。
(當(dāng)然,如果我是外包,怎么方便怎么來。。。。)
回歸本源,在android里面,adapter(適配器)到底是干嘛用的?
適配器就是用來將數(shù)據(jù)(data)和視圖(view)綁定的工具,如果更加簡(jiǎn)單的說,就是根據(jù)需求展示不同數(shù)據(jù)集數(shù)據(jù),再更簡(jiǎn)單的說,他喵的就是一個(gè)渲染器。(←這個(gè)非權(quán)威描述,需要權(quán)威描述的請(qǐng)自行谷歌“適配器模式”)
那么作為一個(gè)渲染器,我們需要他做的,就是渲染畫面就好了,其他的不要管(實(shí)現(xiàn)控制和展示兩者分離,易于維護(hù))。
于是我們就有了以下方案:
- 抽象一個(gè)viewholder,該holder用于告訴adapter:“我的心是屬于這個(gè)類型的”(這個(gè)類型用這個(gè)xml布局)
- adapter用一個(gè)集合,存入所有類型的holder,并實(shí)現(xiàn)將viewType和holder對(duì)應(yīng)起來。
- adapter只負(fù)責(zé)渲染,其他的在holder里面完成。
文字版也許不那么清晰,我們看看思維導(dǎo)圖吧:

大致結(jié)構(gòu)如上
接下來實(shí)現(xiàn)一下大致的結(jié)構(gòu)雛形,具體的代碼如下:
public interface BaseItemView<T> {
int getViewRes();
void onFindView(@NonNull View parent);
void onBindData(final int position, @NonNull View v, @NonNull T data,final int dynamicType);
Activity getActivityContext();
void setActivityContext(Activity context);
}
我們采取接口的方式,該接口實(shí)現(xiàn)以下兩個(gè)功能:(此處沒有遵循單一職責(zé)原則)
- 得到對(duì)應(yīng)的布局id
- 數(shù)據(jù)綁定
然后抽象我們的adapter:
/**
* Created on 2016/2/16.
* 適配器抽象
*/
public abstract class CircleBaseAdapter<T> extends BaseAdapter {
private static final String TAG = "FriendCircleAdapter";
//數(shù)據(jù)
protected List<T> datas = new ArrayList<>();
//類型集合
protected HashMap<Integer, Class<? extends BaseItemView<T>>> itemInfos;
protected Activity context;
protected LayoutInflater mInflater;
public CircleBaseAdapter(Activity context, Builder<T> mBuilder) {
this.context = context;
mInflater = LayoutInflater.from(context);
datas.clear();
datas.addAll(mBuilder.datas);
itemInfos = mBuilder.itemInfos;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public T getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public abstract int getItemViewType(int position);
@Override
public int getViewTypeCount() {return 15;}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final int dynamicType = getItemViewType(position);
BaseItemView view = null;
if (convertView == null) {
Class viewClass = itemInfos.get(dynamicType);
Log.d(TAG,""+viewClass);
try {
view = (BaseItemView) viewClass.newInstance();
} catch (InstantiationException e) {
Log.e(TAG, "反射創(chuàng)建失?。。?!");
e.printStackTrace();
} catch (IllegalAccessException e) {
Log.e(TAG, "反射創(chuàng)建失?。。。?);
e.printStackTrace();
}
if (view != null) {
convertView = mInflater.inflate(view.getViewRes(), parent, false);
convertView.setTag(view);
}
else {
throw new NullPointerException("view是空的哦~");
}
}
else {
view = (BaseItemView) convertView.getTag();
}
view.setActivityContext(context);
view.onFindView(convertView);
view.onBindData(position, convertView, getItem(position), dynamicType);
return convertView;
}
public static class Builder<T> {
private HashMap<Integer, Class<? extends BaseItemView<T>>> itemInfos;
private Activity context;
private List<T> datas;
public Builder() {
itemInfos = new HashMap<>();
}
public Builder(List<T> datas) {
itemInfos = new HashMap<>();
this.datas = datas;
}
public Builder addType(int type, Class<? extends BaseItemView<T>> viewClass) {
itemInfos.put(type, viewClass);
return this;
}
public Builder setDatas(List<T> datas) {
this.datas = datas;
return this;
}
public Builder build() {return this;}
}
}
我們主要講注意力放到getView方法里面:
可以看得出,我們的getView其實(shí)大致來說都是跟平時(shí)寫法一樣的,都是
if(converview==null){
...viewholder
}else{
viewholder=converview.getTag();
}
不過有點(diǎn)不同的是這里我們用反射來將viewholder給new一個(gè)出來,這樣我們就可以將所有的其他操作都放在對(duì)應(yīng)的class里面執(zhí)行,比如實(shí)現(xiàn)點(diǎn)擊接口什么的。
這么做的好處就是。。。。。起碼代碼看起來沒那么多對(duì)吧- -
其次就是易于維護(hù)。
另外有一點(diǎn)需要注意的是getViewTypeCount()必須比getItemViewType要大,否則會(huì)出現(xiàn)越界的error,而我們上一篇定義的類型最大的是14,所以我們給個(gè)15的定值。(這里實(shí)現(xiàn)還是不太好啊。。。。以后做優(yōu)化)
存儲(chǔ)集合的地方我們使用builder模式,畢竟不知道啥時(shí)候也許會(huì)增加一些新的viewType,對(duì)吧。
我們集合存的是一個(gè)繼承BaseItemView<T>的類,所以我們使用這個(gè)adapter只需要實(shí)現(xiàn)這個(gè)接口就可以 了。
接下來我們實(shí)現(xiàn)一下這個(gè)接口:
public abstract class BaseItemDelegate<T> implements BaseItemView<T>, View.OnClickListener {
protected Activity context;
public BaseItemDelegate() {
}
public BaseItemDelegate(Activity context) {
this.context = context;
}
@Override
public void onClick(View v) {
}
@Override
public void onBindData(int position, @NonNull View v, @NonNull T data, final int dynamicType) {
// TODO: 2016/2/16 初始化共用部分
bindData(position, v, data, dynamicType);
}
@Override
public Activity getActivityContext() {
return context;
}
@Override
public void setActivityContext(Activity context) {
this.context=context;
}
protected abstract void bindData(int position, @NonNull View v, @NonNull T data, final int dynamicType);
}
因?yàn)橥ㄟ^反射方法創(chuàng)建,所以我們需要保留一個(gè)空的構(gòu)造器哦,這個(gè)抽象類將作為基本的item,這里以后會(huì)實(shí)現(xiàn)共有部分的操作,這樣我們的其他不同的view只需要繼承它就可以了。
實(shí)現(xiàn)完這幾個(gè)類,最后就是進(jìn)行測(cè)試了。
新建一個(gè)adapter繼承我們的父類:
public class FriendCircleAdapterTest<TestBean> extends CircleBaseAdapter {
private static final String TAG = "FriendCircleAdapterTest";
public FriendCircleAdapterTest(Activity context, Builder mBuilder) {
super(context, mBuilder);
}
@Override
public int getItemViewType(int position) {
razerdp.friendcircle.test.TestBean bean= (razerdp.friendcircle.test.TestBean) datas.get(position);
Log.d(TAG,"當(dāng)前type------- "+bean.type);
return bean.type;
}
}
然后新建一個(gè)view繼承我們的baseitem類,并實(shí)現(xiàn)初步的點(diǎn)擊方法:
public class TestItem1 extends BaseItemDelegate<TestBean> {
private TextView testTx;
private Button testBtn;
public TestItem1() {}
@Override
protected void bindData(int position, @NonNull View v, @NonNull TestBean data, int dynamicType) {
testBtn.setTag(data);
testTx.setText(data.testStr);
}
@Override
public int getViewRes() {
return R.layout.test_type_one;
}
@Override
public void onFindView(@NonNull View parent) {
testTx = (TextView) parent.findViewById(R.id.test_1);
testBtn = (Button) parent.findViewById(R.id.btn_test_1);
testTx.setOnClickListener(this);
testBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
super.onClick(v);
switch (v.getId()) {
case R.id.test_1:
break;
case R.id.btn_test_1:
TestBean bean = (TestBean) v.getTag();
Toast.makeText(getActivityContext(), "你點(diǎn)的是類型 " + bean.type, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
最后放進(jìn)我們的測(cè)試數(shù)據(jù):

如你所見,我們的adapter需要通過builder弄進(jìn)去,builder里面的addType方法也很直觀:類型-對(duì)應(yīng)的class。對(duì)于使用者來說,需要用到的就這么多,adapter不用管,我們管理的只是對(duì)應(yīng)類的操作就可以了。
本篇結(jié)束。