一起撸个朋友圈吧(step3) - ListAdapter篇

项目地址:https://github.com/razerdp/FriendCircle
一起撸个朋友圈吧这是本文所处文集,所有更新都会在这个文集里面哦,欢迎关注

上篇链接:http://www.jianshu.com/p/dc5782a494b5
下篇链接:http://www.jianshu.com/p/1f85d3978bb5

文章开始之前,谈谈JSON这个数据合集的问题吧,关于JSON,因为在下在公司实习的时候写了好多次解析,说实话,在发现GsonFormat插件这个东东之前,,,我写JSON解析都是这么写的。。:

xxx=json.optXXX("xxx",xxx);

唉,现在真的挺佩服当时的耐心,面对这么多的JSON Array,Object我居然有如此耐心一个一个去手动解析。

这个一起撸朋友圈文章写到这里,其实我也发现了,似乎没有后端支持,还真挺难搞的,同时因为我的毕业设计也需要一些后端的支持,所以在下决定两者同时并行。。。希望有一天,可以撸一个简单的服务器来支撑我们这个项目-V-


废话完了,进入今天的主题吧

首先感谢同事的思想(这是他的git哦:https://github.com/wenjiahui ),这篇文章扩展于他的idea.


关于一个Adapter,我们应该也写过很多很多了,无非就是继承一个BaseAdapter,实现那些getXXX,然后getView里面用viewholder装起来。

确实,我们的朋友圈adapter也是基于这个思想,但是略有改变。

其一,我们有多个type,比如图文,文字什么的,我们需要区分这些type。这个好办,ItemViewType解决,嗯这没问题。

其二,我们有各种各样的ClickEvent,比如点击头像,点击图片,长按文字复制,点击评论,点击名字什么的。这个好办,不同的clickListener,嗯,这么问题。

那么,现在问题来了,如果我们在adapter真按照平时的写法来实现上面两点,那么我们将会看到这样的代码:

...getItem什么的此处略过
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder1 xxx;//图文viewholder
    ViewHolder2 xxx;//文字viewholder
    ViewHolder3 xxx;//网页viewholder
    ...好多viewholder
    ...好多viewholder初始化
    switch(getItemViewType(position)){
        case xxx:
            ...好多代码;
            ...xxx.setOnClickListener(xxx);//点击xxx的listener,下面也许还有n个
          break;
        case xxx:
            ...好多代码;
            ...xxx.setOnClickListener(xxx);//点击xxx的listener,下面也许还有n个
          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) }
...
}

我相信,没有几个人愿意看到一个adapter一千多行或者两千行代码吧。。。其中还混有N个viewholder和N个点击事件。
(当然,如果我是外包,怎么方便怎么来。。。。)

回归本源,在android里面,adapter(适配器)到底是干嘛用的?
适配器就是用来将数据(data)和视图(view)绑定的工具,如果更加简单的说,就是根据需求展示不同数据集数据,再更简单的说,他喵的就是一个渲染器。(←这个非权威描述,需要权威描述的请自行谷歌“适配器模式”)

那么作为一个渲染器,我们需要他做的,就是渲染画面就好了,其他的不要管(实现控制和展示两者分离,易于维护)。

于是我们就有了以下方案:

  • 抽象一个viewholder,该holder用于告诉adapter:“我的心是属于这个类型的”(这个类型用这个xml布局)
  • adapter用一个集合,存入所有类型的holder,并实现将viewType和holder对应起来。
  • adapter只负责渲染,其他的在holder里面完成。

文字版也许不那么清晰,我们看看思维导图吧:

导图

大致结构如上

接下来实现一下大致的结构雏形,具体的代码如下:
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);
}

我们采取接口的方式,该接口实现以下两个功能:(此处没有遵循单一职责原则)

  • 得到对应的布局id
  • 数据绑定
然后抽象我们的adapter:
/**
 * Created on 2016/2/16.
 * 适配器抽象
 */
public abstract class CircleBaseAdapter<T> extends BaseAdapter {
    private static final String TAG = "FriendCircleAdapter";
    //数据
    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, "反射创建失败!!!");
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                Log.e(TAG, "反射创建失败!!!");
                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其实大致来说都是跟平时写法一样的,都是

if(converview==null){
...viewholder
}else{
viewholder=converview.getTag();
}

不过有点不同的是这里我们用反射来将viewholder给new一个出来,这样我们就可以将所有的其他操作都放在对应的class里面执行,比如实现点击接口什么的。

这么做的好处就是。。。。。起码代码看起来没那么多对吧- -

其次就是易于维护。

另外有一点需要注意的是getViewTypeCount()必须比getItemViewType要大,否则会出现越界的error,而我们上一篇定义的类型最大的是14,所以我们给个15的定值。(这里实现还是不太好啊。。。。以后做优化)

存储集合的地方我们使用builder模式,毕竟不知道啥时候也许会增加一些新的viewType,对吧。

我们集合存的是一个继承BaseItemView<T>的类,所以我们使用这个adapter只需要实现这个接口就可以 了。

接下来我们实现一下这个接口:

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);
}

因为通过反射方法创建,所以我们需要保留一个空的构造器哦,这个抽象类将作为基本的item,这里以后会实现共有部分的操作,这样我们的其他不同的view只需要继承它就可以了。

实现完这几个类,最后就是进行测试了。

新建一个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,"当前type------- "+bean.type);
        return bean.type;
    }
}

然后新建一个view继承我们的baseitem类,并实现初步的点击方法:

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(), "你点的是类型  " + bean.type, Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }
}

最后放进我们的测试数据:

测试数据

如你所见,我们的adapter需要通过builder弄进去,builder里面的addType方法也很直观:类型-对应的class。对于使用者来说,需要用到的就这么多,adapter不用管,我们管理的只是对应类的操作就可以了。

本篇结束。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容