一起擼個(gè)朋友圈吧(step3) - ListAdapter篇

項(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)圖吧:

導(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ù):

測(cè)試數(shù)據(jù)

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

本篇結(jié)束。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容