# Adapter

## 单布局

ListView/GridView + Adapter可以说是Android程序员最常用的组件了. 我们应该尽量简化Adapter的代码以提高开发效率. 首先来看看不做任何封装的Adapter需要哪些步骤:

1. 继承自BaseAdpter
2. 维护一个List数据并实现4个抽象方法
3. 声明一个ViewHolder
4. 在getView中判断convertView是否为空, 为空则inflate一个布局, 初始化ViewHolder, 初始化控件, 并将ViewHolder通过setTag设置到convertView中.
5. 如果不为空则通过getTag将ViewHolder取出来
6. 为控件设置值

可以看到需要做的事情还是很多的. utils包中有一个ViewHolder类, 此类可以代替自己声明的ViewHolder, 并且不用写将ViewHolder与convertView绑定的代码. 控件可以直接通过如下方式声明:

```
TextView textView = ViewHolder.get(convertView, R.id.textView);
```

ViewHolder中使用了SparseArray来缓存视图, 内部也是用过给convertView设置tag来达到缓存的目的. 并且通过泛型来避免类型转换. 整体逻辑很简单:

```
public class ViewHolder {
    @SuppressWarnings("unchecked")
    public static <T extends View> T get(View view, int id) {
        SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray<>();
            view.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (T) childView;
    }
}
```

> 下面的内容详细可以参考 [打造万能的ListView GridView 适配器](http://blog.csdn.net/lmj623565791/article/details/38902805)

虽然有了ViewHolder能减少很多代码量, 但是这不是极限, 还可以继续减少. 比如内部维护的List数据, 另外三个抽象方法等都是可以不用写的. 这里我们引用GitHub上的一个开源工具 [base adapter helper](https://github.com/JoanZapata/base-adapter-helper). 现在来看看如何使用base adapter helper:

```
public class HomeAdapter extends QuickAdapter<Attraction> {

    public HomeAdapter(Context context, int layoutResId) {
        super(context, layoutResId);
    }

    @Override
    protected void convert(BaseAdapterHelper helper, Attraction item) {
        helper.setText(R.id.tv_name, item.name)
                .setText(R.id.tv_des, item.profile)
                .setText(R.id.tv_distance,
                        String.format(context.getResources().getString(R.string.item_home_KM),
                                Attraction.formatDistance(item.distance)))
    }
}
```

可以调用如下方法修改Adapter的数据

```
homeAdapter.add(data);
homeAdapter.addAll(list);
homeAdapter.removeAll();
homeAdapter.remove(0);
homeAdapter.set(0, data);
...
```

base adapter helper的原理在此处就不赘述了, 参考 [Android base-adapter-helper 源码分析与扩展](http://blog.csdn.net/lmj623565791/article/details/44014941). 使用base adapter helper可以减少大量重复的代码, 也可以通过扩展BaseAdapterHelper以适应自己的编程习惯.

接下来看看base adapter helper的getItemId方法的实现:

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

很多时候, getItemId的返回值都是position. 如果我们需要拿到实体类的id, 就必须这么写:

```
adapter.getItem(position).id;
```

通常情况下, ListView中的某些控件是需要与网络API交互的, 而基本上网络API需要的只是一条数据的id. 因此我改造了getItemId方法, 在某些情况下, 此方法能直接返回实体类中的id.

```
/**
 * 配合{@link QuickAdapter}使用, 传入{@link QuickAdapter}中的数据类型如果实现此接口,
 * 则可以直接调用{@link QuickAdapter#getItemId(int)}来获取数据的id.
 */
public interface IdObject {
    long getId();
}
```

```
@Override
public long getItemId(int position) {
    T data = this.data.get(position);
    return data instanceof IdObject ? ((IdObject) data).getId() : position;
}
```

上述的某些情况就是指T类型实现了IdObject接口. 以上就是对Android适配器的简化及优化. 但是base adapter helper使用虽然简单, 也还是有不足的地方. 比如不支持多布局, 只支持BaseAdapter, 不支持其他类型比如ViewPager的Adapter等.

以上就是单布局的基本原理与使用方法. 单布局是APP中最常用的, 下面介绍不是很常见的多布局的封装.

## 多布局

多布局是指一个适配器中使用一个以上的布局文件. 主要是依赖于覆写`BaseAdapter`中的`getViewTypeCount`与`getItemViewType`方法. 多布局使用了代理, 将convert方法代理给多个代理类去实现, 有兴趣的可以[看这里](https://github.com/sockeqwe/AdapterDelegates), 这里只讲使用方法. 直接上代码:

```
final QuickMultiAdapter<Integer> adapter = new QuickMultiAdapter<>(this);
adapter.addItemViewDelegate(new LeftDelegate());
adapter.addItemViewDelegate(new RightDelegate());

listView.setAdapter(adapter);
adapter.replaceAll(getData());
```

```
    class LeftDelegate implements ItemViewDelegate<Integer> {

        @Override public int getItemViewLayoutId() {
            return R.layout.i_text;
        }

        @Override public boolean isForViewType(Integer item, int position) {
            return item % 2 == 0;
        }

        @Override public void convert(BaseAdapterHelper helper, Integer item, int position) {
            helper.setText(R.id.text, item + "");
        }
    }
```

```
    class RightDelegate implements ItemViewDelegate<Integer> {

        @Override public int getItemViewLayoutId() {
            return R.layout.i_text_right;
        }

        @Override public boolean isForViewType(Integer item, int position) {
            return item % 2 != 0;
        }

        @Override public void convert(BaseAdapterHelper helper, Integer item, int position) {
            helper.setText(R.id.text, item + "");
        }
    }
```

效果:

![效果](/files/-LsLm8CcVkrIMh4kP6fl)

两个布局的差别就是一个TextView的gravity是left, 同时有margin值, 另一个的gravity为right, 没有margin值. 如果是双数则显示gravity是left的TextView, 否则显示gravity是right的TextView. 代码也比较好理解, 接下来看看Delegate接口的声明以及每个方法的含义:

```
/**
 * Adapter中多布局代理
 * @param <T> 数据源类型
 * @param <H> ViewHolder类型
 */
public interface BaseItemViewDelegate<T, H extends BaseAdapterHelper> {

    /** 布局资源id **/
    int getItemViewLayoutId();

    /** 判断该position是否要加载此类型的布局 **/
    boolean isForViewType(T item, int position);

    /**
     * 当需要条目将被展示到界面上时, 通过此方法适配界面
     * @param helper ViewHolder
     * @param item 数据
     * @param position 位置
     */
    void convert(H helper, T item, int position);
}
```

ItemViewDelegate继承了BaseItemViewDelegate, 同时声明H为BaseAdapterHelper, 所以在不需要自定义AdapterHelper的情况下, 建议直接使用ItemViewDelegate:

```
/**
 * ViewHolder类型为BaseAdapterHelper的快捷代理接口
 */
public interface ItemViewDelegate<T> extends BaseItemViewDelegate<T, BaseAdapterHelper> {}
```

## RecyclerView

作为目前超级灵活超级NB的杀手级控件 - RecyclerView, 怎么能少了它呢? 直接上代码:

```
final RecyclerAdapter<Integer> adapter = new RecyclerAdapter<Integer>(this, R.layout.i_text) {
    @Override protected void convert(BaseAdapterHelper helper, Integer item, int position) {
        helper.setText(R.id.text, item + "");
    }
};
```

使用方法与ListView/GridView一样, 只不过继承从`QuickAdapter`变成`RecyclerAdapter`. 多布局也一样, 区别只是从`QuickMultiAdapter`换成`RecyclerMultiAdapter`. 下面看一个多布局的例子:

首先是效果:

![效果](/files/-LsLm8CeDAhpmlKfcZul)

示例中有两种布局, 一种是日期, 一种是课程详情. 日期占满一行, 详情占半行. XML以及实体省略:

```
RecyclerMultiAdapter<Playback> adapter = new RecyclerMultiAdapter<>(getActivity());
adapter.addItemViewDelegate(new DateDelegate());
adapter.addItemViewDelegate(new ContentDelegate());

GridLayoutManager manager = new GridLayoutManager(getActivity(), 2);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override public int getSpanSize(int position) {
        return adapter.getItem(position).isDate ? 2 : 1;
    }
});

recycler.setLayoutManager(manager);
recycler.addItemDecoration(new PlaybackItemDecoration());
recycler.setAdapter(adapter);
```

```
    class DateDelegate implements ItemViewDelegate<Playback> {

        @Override public int getItemViewLayoutId() {
            return R.layout.i_playback_date;
        }

        @Override public boolean isForViewType(Playback item, int position) {
            return item.isDate;
        }

        @Override public void convert(BaseAdapterHelper helper, Playback item, int position) {
            helper.setText(R.id.tv_date, item.formattedDate);
        }
    }

    class ContentDelegate implements ItemViewDelegate<Playback> {

        @Override public int getItemViewLayoutId() {
            return R.layout.i_playback;
        }

        @Override public boolean isForViewType(Playback item, int position) {
            return !item.isDate;
        }

        @Override public void convert(BaseAdapterHelper helper, Playback item, int position) {
            helper.setText(R.id.tv_class_index, item.getClassNumberHint())
                    .setText(R.id.tv_teacher, "教师: " + item.teacher)
                    .setText(R.id.tv_time, "时间: " + item.getFormattedTimePeriod())
                    .setTag(R.id.parent, position)
                    .setOnClickListener(R.id.parent, listener);
        }
    }
```

```
    class PlaybackItemDecoration extends RecyclerView.ItemDecoration {
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view);
            Playback playback = adapter.getItem(position);
            if (playback.isDate) {
                outRect.set(0, divider, 0, divider);
                if (position == 0) outRect.top = 0;
            } else {
                outRect.set(divider, divider, divider, divider);
                if (playback.subPosition % 2 == 0)
                    outRect.left = divider * 2;
                else
                    outRect.right = divider * 2;
            }
        }
    }
```

除了Adapter的代码外, 其他均是RecyclerView的基本用法, 如使用SpanSizeLookup来设置每个条目占用的单元数, 使用ItemDecoration来控制条目之间的间隔等.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryan-8.gitbook.io/android-architecture-journey/utilities/adapter.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
