[TOC]
# 优化Adapter代码
item view 如下:
![](https://img.kancloud.cn/e3/0f/e30f75815309f19ac327897f07b67d9d_674x224.png)
代码如下:
```java
public class QuickAdapter extends BaseQuickAdapter<Status, BaseViewHolder> {
public QuickAdapter() {
super(R.layout.tweet, DataServer.getSampleData());
}
@Override
protected void convert(BaseViewHolder viewHolder, Status item) {
viewHolder.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setVisible(R.id.tweetRT, item.isRetweet())
.linkify(R.id.tweetText);
Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) viewHolder.getView(R.id.iv));
}
}
```
可以看到,仅仅需要继承BaseQuickAdapter,在构造器中传入item view的布局,并重写convert方法即可。
## 优化Adapter代码源码分析
直接来看BaseQuickAdapter的构造方法和convert方法:
构造方法:
```java
public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> {
public BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data) {
this.mData = data == null ? new ArrayList<T>() : data;
if (layoutResId != 0) {
this.mLayoutResId = layoutResId;
}
}
//...
protected abstract void convert(@NonNull K helper, T item);
@Override
public void onBindViewHolder(@NonNull K holder, int position) {
int viewType = holder.getItemViewType();
switch (viewType) {
case 0:
convert(holder, getItem(position - getHeaderLayoutCount()));
break;
//...
}
}
}
```
可以看到我们熟悉的代码了,在BaseQuickAdapter的构造方法中初始化了item布局和数据源;convert方法是一个抽象方法,在onBindViewHolder方法中进行调用。
# 点击事件
如果是item的点击事件,只要为Adapter设置clickListener即可:
```java
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
//...
}
});
```
而为item里面的控件设置点击事件才是我们经常用到的
```java
@Override
protected void convert(BaseViewHolder viewHolder, Status item) {
viewHolder.addOnClickListener(R.id.tweetAvatar)
.addOnClickListener(R.id.tweetName);
}
// Adapter设置监听器
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
switch (view.getId()) {
case R.id.tweetAvatar:
break;
case R.id.tweetName:
break;
}
}
});
```
## 点击事件源码分析
在上一节的源码分析中,我们在convert方法中可以拿到ViewHolder,这个ViewHolder继承自BaseViewHolder,来看看源码:
```java
public class BaseViewHolder extends RecyclerView.ViewHolder {
private final SparseArray<View> views;
public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
TextView view = getView(viewId);
view.setText(value);
return this;
}
public BaseViewHolder addOnClickListener(@IdRes final int ...viewIds) {
for (int viewId : viewIds) {
childClickViewIds.add(viewId);
final View view = getView(viewId);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
adapter.getOnItemChildClickListener().onItemChildClick(adapter, v, position);
}
});
}
return this;
}
public <T extends View> T getView(@IdRes int viewId) {
View view = views.get(viewId);
if (view == null) {
// 如果未缓存,则从视图中寻找,并缓存
view = itemView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
}
```
BaseViewHolder采用SparseArray将所有View缓存起来,在需要使用的时候调用getView方法进行获取。上面源码只列出了setText方法,其他如setVisible、setBackgroundColor、setImageDrawable等均类似。
值得注意的一点是关于点击事件的处理。最新版本的代码中废弃了`view.setOnClickListener(listener)`直接设置监听器的做法,改为调用BaseQuickAdapter的OnItemChildClickListener的相应方法。这种操作将点击事件的设定由Adapter内部,改到Adapter外部,也就是操作Adapter对象来设置点击事件,让Adapter来专心做item的适配工作。
# item展示动画
```java
// 打开默认动画
public void openLoadAnimation() {}
// 打开自定义动画
public void openLoadAnimation(BaseAnimation animation) {}
// 打开内置的5种动画
public void openLoadAnimation(@AnimationType int animationType) {}
```
## item展示动画源码分析
```java
public void openLoadAnimation() {
this.mOpenAnimationEnable = true;
}
public void openLoadAnimation(BaseAnimation animation) {
this.mOpenAnimationEnable = true;
this.mCustomAnimation = animation;
}
```
打开动画仅仅是将mOpenAnimationEnable置为true,那么是什么时候添加的动画呢?
通过阅读源码可知,
```java
public void onViewAttachedToWindow(@NonNull K holder) {
//...
addAnimation(holder);
}
```
在Adapter创建出的itemView被添加到Window时,会调用addAnimation方法。
```java
private void addAnimation(RecyclerView.ViewHolder holder) {
if (mOpenAnimationEnable) {
//...
if (mCustomAnimation != null) {
animation = mCustomAnimation;
} else {
animation = mSelectAnimation;
}
for (Animator anim : animation.getAnimators(holder.itemView)) {
startAnim(anim, holder.getLayoutPosition());
}
}
}
}
```
接下来我们再仔细分析分析内置的动画,具体是如何展示的呢。内置动画全部实现了BaseAnimation接口:
```java
public interface BaseAnimation {
Animator[] getAnimators(View view);
}
```
来看看随便一个实现类:
```java
public class SlideInBottomAnimation implements BaseAnimation {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0)
};
}
}
```
是的,直接使用属性动画来完成的操作。下面我们再整理一下整个步骤:
* 为Adapter设置openLoadAnimation方法打开动画
* Adapter创建出的itemView在添加到Window展示时,为itemView添加属性动画
# 添加HeadView和FootView
ListView可以方便的添加HeadView和FootView,下面看看BaseRecyclerViewAdapterHelper是如何使用及实现的。
```java
public int addHeaderView(View header) {}
public int addFooterView(View footer) {}
public void removeHeaderView(View header) {}
public void removeFooterView(View footer) {}
public void removeAllHeaderView() {}
public void removeAllFooterView() {}
```
## 添加HeadView和FootView源码分析
来看看addHeaderView的源码:
```java
private LinearLayout mHeaderLayout;
public int addHeaderView(View header,final int index, int orientation) {
if (mHeaderLayout == null) {
mHeaderLayout = new LinearLayout(header.getContext());
if (orientation == LinearLayout.VERTICAL) {
mHeaderLayout.setOrientation(LinearLayout.VERTICAL);
mHeaderLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
} else {
mHeaderLayout.setOrientation(LinearLayout.HORIZONTAL);
mHeaderLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT));
}
}
final int childCount = mHeaderLayout.getChildCount();
int mIndex =index;
if (index < 0 || index > childCount) {
mIndex = childCount;
}
mHeaderLayout.addView(header, mIndex);
if (mHeaderLayout.getChildCount() == 1) {
int position = getHeaderViewPosition();
if (position != -1) {
notifyItemInserted(position);
}
}
return mIndex;
}
```
代码有点长,但是逻辑很简单,首先判断mHeaderLayout是否为空,mHeaderLayout是一个LinearLayout,用来放HeadView的,由此判断HeadView可以添加多个。创建一个LinearLayout并把HeadView添加进去即可。mHeaderLayout又是如何展示出来的呢?
```java
public K onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case HEADER_VIEW:
ViewParent headerLayoutVp = mHeaderLayout.getParent();
// 如果headerLayoutVp有父布局的话,进行移除
if (headerLayoutVp instanceof ViewGroup) {
((ViewGroup) headerLayoutVp).removeView(mHeaderLayout);
}
baseViewHolder = createBaseViewHolder(mHeaderLayout);
break;
//...
}
baseViewHolder.setAdapter(this);
return baseViewHolder;
}
```
原来在onCreateViewHolder方法中,根据ViewType类型来创建ViewHolder即可。
# 拖拽和滑动删除item
使用拖拽和滑动删除item功能需要Adapter继承BaseItemDraggableAdapter。
```java
ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
// 开启拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);
// 开启滑动删除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
```
## 拖拽和滑动删除item源码分析
### 拖拽源码分析
从enableDragItem方法看起:
```java
public void enableDragItem(@NonNull ItemTouchHelper itemTouchHelper, int toggleViewId, boolean dragOnLongPress) {
itemDragEnabled = true;
mItemTouchHelper = itemTouchHelper;
// 设置切换id
setToggleViewId(toggleViewId);
// 设置是否需要长按,才能拖动
setToggleDragOnLongPress(dragOnLongPress);
}
```
先来看看方法的三个参数,第一个参数ItemTouchHelper是一个系统类,用于RecyclerView的item移动;第二个参数是需要按住的子view的id;第三个参数是设置是否需要长按。来看看setToggleDragOnLongPress方法:
```java
public void setToggleDragOnLongPress(boolean longPress) {
mDragOnLongPress = longPress;
if (mDragOnLongPress) {
mOnToggleViewLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mItemTouchHelper != null && itemDragEnabled) {
mItemTouchHelper.startDrag((RecyclerView.ViewHolder) v.getTag(R.id.BaseQuickAdapter_viewholder_support));
}
return true;
}
};
} else {
//...
}
}
```
初始化OnToggleViewLongClickListener了,那OnToggleViewLongClickListener又是在那里用到的呢?
```java
public void onBindViewHolder(@NonNull K holder, int position) {
int viewType = holder.getItemViewType();
if (hasToggleView()) {
View toggleView = holder.getView(mToggleViewId);
if (toggleView != null) {
toggleView.setTag(R.id.BaseQuickAdapter_viewholder_support, holder);
if (mDragOnLongPress) {
toggleView.setOnLongClickListener(mOnToggleViewLongClickListener);
} else {
toggleView.setOnTouchListener(mOnToggleViewTouchListener);
}
}
}
}
```
可以看到,在创建ViewHolder的时候进行了绑定监听器。开启拖拽后,下面我们来看看拖拽监听器:
```java
public void onItemDragStart(RecyclerView.ViewHolder viewHolder) {
if (mOnItemDragListener != null && itemDragEnabled) {
mOnItemDragListener.onItemDragStart(viewHolder, getViewHolderPosition(viewHolder));
}
}
public void onItemDragMoving(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
//...
if (mOnItemDragListener != null && itemDragEnabled) {
mOnItemDragListener.onItemDragMoving(source, from, target, to);
}
}
public void onItemDragEnd(RecyclerView.ViewHolder viewHolder) {
if (mOnItemDragListener != null && itemDragEnabled) {
mOnItemDragListener.onItemDragEnd(viewHolder, getViewHolderPosition(viewHolder));
}
}
```
设置好的拖拽监听器分别在上述三个方法中调用,这三个方法又是在何时调用的呢?阅读源码可知,在ItemDragAndSwipeCallback方法进行调用,ItemDragAndSwipeCallback实现了ItemTouchHelper.Callback接口,完成了具体的拖拽滑动工作,ItemDragAndSwipeCallback的源码后期再分析!
### 滑动删除item源码分析
```java
public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder) {
if (mOnItemSwipeListener != null && itemSwipeEnabled) {
mOnItemSwipeListener.onItemSwipeStart(viewHolder, getViewHolderPosition(viewHolder));
}
}
public void onItemSwipeClear(RecyclerView.ViewHolder viewHolder) {
if (mOnItemSwipeListener != null && itemSwipeEnabled) {
mOnItemSwipeListener.clearView(viewHolder, getViewHolderPosition(viewHolder));
}
}
public void onItemSwiped(RecyclerView.ViewHolder viewHolder) {
if (mOnItemSwipeListener != null && itemSwipeEnabled) {
mOnItemSwipeListener.onItemSwiped(viewHolder, pos);
}
}
public void onItemSwiping(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive) {
if (mOnItemSwipeListener != null && itemSwipeEnabled) {
mOnItemSwipeListener.onItemSwipeMoving(canvas, viewHolder, dX, dY, isCurrentlyActive);
}
}
```
OnItemSwipeListener在上述几个方法中调用,上面几个方法在ItemDragAndSwipeCallback中调用,执行真正的滑动删除工作。
# 上拉下拉更新
1、上拉加载使用代码
```java
mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
loadMore();
}
});
private void loadMore() {
new Request(mNextRequestPage, new RequestCallBack() {
@Override
public void success(List<Status> data) {
boolean isRefresh = mNextRequestPage == 1;
setData(isRefresh, data);
}
@Override
public void fail(Exception e) {
mAdapter.loadMoreFail();
}
}).start();
}
// setData方法上拉加载、下拉刷新共用
private void setData(boolean isRefresh, List data) {
// 获取到数据了,页数加一
mNextRequestPage++;
final int size = data == null ? 0 : data.size();
// 如果是第一页,也就是重新刷新的
if (isRefresh) {
mAdapter.setNewData(data);
} else {
// 不是第一页,并且有数据
if (size > 0) {
mAdapter.addData(data);
}
}
if (size < PAGE_SIZE) {
// 下拉刷新时,如果第一页数据不够一页,就隐藏【没有更多数据布局】
// 如果是后面的页数,就显示出【没有更多数据布局】
mAdapter.loadMoreEnd(isRefresh);
Toast.makeText(this, "no more data", Toast.LENGTH_SHORT).show();
} else {
// 成功加载一页数据
mAdapter.loadMoreComplete();
}
}
```
可以看到,在成功获取到数据后,首先为Adapter添加数据,然后在回调loadMore相应的方法。
2、下拉刷新代码
```java
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refresh();
}
});
private void refresh() {
mNextRequestPage = 1;
// 这里的作用是防止下拉刷新的时候还可以上拉加载
mAdapter.setEnableLoadMore(false);
new Request(mNextRequestPage, new RequestCallBack() {
@Override
public void success(List<Status> data) {
setData(true, data);
mAdapter.setEnableLoadMore(true);
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void fail(Exception e) {
Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show();
mAdapter.setEnableLoadMore(true);
mSwipeRefreshLayout.setRefreshing(false);
}
}).start();
}
```
## 上拉下拉更新源码分析
1、上拉加载源码分析
关于上拉加载我们主要分析下面这两个问题:
* OnLoadMoreListener接口的onLoadMoreRequested 方法什么时候回调
* Adapter的loadMoreFail、loadMoreEnd、loadMoreComplete分别做了什么
先来看看setOnLoadMoreListener方法:
```java
public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener, RecyclerView recyclerView) {
openLoadMore(requestLoadMoreListener);
// 绑定RecyclerView到当前Adapter
if (getRecyclerView() == null) {
setRecyclerView(recyclerView);
}
}
// 设置监听器
private void openLoadMore(RequestLoadMoreListener requestLoadMoreListener) {
this.mRequestLoadMoreListener = requestLoadMoreListener;
mNextLoadEnable = true;
mLoadMoreEnable = true;
mLoading = false;
}
```
mRequestLoadMoreListener又是什么时候被调用到的呢,看源代码:
```java
// autoLoadMore 方法会在onBindViewHolder方法
private void autoLoadMore(int position) {
if (doNotNeedShowLoadMore) {
return;
}
//...
mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING);
if (!mLoading) {
mLoading = true;
if (getRecyclerView() != null) {
getRecyclerView().post(new Runnable() {
@Override
public void run() {
mRequestLoadMoreListener.onLoadMoreRequested();
}
});
} else {
mRequestLoadMoreListener.onLoadMoreRequested();
}
}
}
```
可以看到,在onBindViewHolder方法中,会调用autoLoadMore方法来判断是否需要请求加载更多,如需要则请求。接下来看看Adapter的loadMoreFail、loadMoreEnd、loadMoreComplete分别做了什么。
```java
public void loadMoreEnd(boolean gone) {
mLoading = false;
mNextLoadEnable = false;
mLoadMoreView.setLoadMoreEndGone(gone);
// 是否隐藏LoadMoreView
if (gone) {
notifyItemRemoved(getLoadMoreViewPosition());
} else {
mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END);
notifyItemChanged(getLoadMoreViewPosition());
}
}
public void loadMoreComplete() {
mLoading = false;
mNextLoadEnable = true;
mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT);
notifyItemChanged(getLoadMoreViewPosition());
}
public void loadMoreFail() {
mLoading = false;
mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_FAIL);
notifyItemChanged(getLoadMoreViewPosition());
}
```
a、loadMoreEnd和loadMoreComplete的不同之处有,一是设置LoadMoreView状态值不同,用于展示不同UI,下一步会说明;二是mNextLoadEnable的值设置不同,这个值用于获取LoadMoreView的数量:
```java
public int getLoadMoreViewCount() {
if (!mNextLoadEnable && mLoadMoreView.isLoadEndMoreGone()) {
return 0;
}
return 1;
}
```
可以看到,loadMoreEnd方法将mNextLoadEnable置为false,在获取LoadMoreView数量时会返回0。
b、调用完成后修改mLoading、mNextLoadEnable的值,并更新loadMoreView的显示状态。LoadMoreView 的convert方法会根据mLoadMoreStatus状态值来展示相应的UI:
```java
public void convert(BaseViewHolder holder) {
switch (mLoadMoreStatus) {
case STATUS_LOADING:
visibleLoading(holder, true);
visibleLoadFail(holder, false);
visibleLoadEnd(holder, false);
break;
case STATUS_FAIL:
visibleLoading(holder, false);
visibleLoadFail(holder, true);
visibleLoadEnd(holder, false);
break;
//...
}
}
```
注:可继承LoadMoreView类来创建自定义LoadMoreView,为Adapter.setLoadMoreView即可。
2、下拉刷新源码分析
下拉刷新的逻辑比较简单,需要注意的是下拉刷新时需要禁用Adapter的上拉加载功能。
# 可折叠item
![](https://img.kancloud.cn/70/64/7064a0d673468bdf2d1e5e17e02795b2_341x535.gif)
Java bean代码
```java
public class Level0Item extends AbstractExpandableItem<Level1Item> {...}
public class Level1Item extends AbstractExpandableItem<Person> {...}
public class Person {...}
```
适配器需要继承自BaseMultiItemQuickAdapter,代码:
```java
public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> {
public ExpandableItemAdapter(List<MultiItemEntity> data) {
super(data);
addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);
addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);
addItemType(TYPE_PERSON, R.layout.item_text_view);
}
@Override
protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {
switch (holder.getItemViewType()) {
case TYPE_LEVEL_0:
....
//set view content
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getAdapterPosition();
if (lv0.isExpanded()) {
collapse(pos);
} else {
expand(pos);
}
}});
break;
case TYPE_LEVEL_1:
// similar with level 0
break;
case TYPE_PERSON:
//just set the content
break;
}
}
```
使用:
```java
public class ExpandableUseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
ArrayList<MultiItemEntity> list = generateData();
ExpandableItemAdapter adapter = new ExpandableItemAdapter(list);
mRecyclerView.setAdapter(adapter);
}
private ArrayList<MultiItemEntity> generateData() {
ArrayList<MultiItemEntity> res = new ArrayList<>();
for (int i = 0; i < lv0Count; i++) {
Level0Item lv0 = new Level0Item(...);
for (int j = 0; j < lv1Count; j++) {
Level1Item lv1 = new Level1Item(...);
for (int k = 0; k < personCount; k++) {
lv1.addSubItem(new Person());
}
lv0.addSubItem(lv1);
}
res.add(lv0);
}
return res;
}
}
```
## 可折叠item源码分析
在适配器ExpandableItemAdapter代码中可以看到,点击事件所执行的操作仅仅是如果是展开的就折叠起来,如果是折叠的就展开,那么我们来看看展开和折叠操作是怎么实现的。
1、折叠源码
先来看看collapse(pos)方法:
```java
// 折叠
public int collapse(@IntRange(from = 0) int position, boolean animate, boolean notify) {
// 获取当前item
IExpandable expandable = getExpandableItem(position);
// 递归折叠所有子item
int subItemCount = recursiveCollapse(position);
// 设置item展开状态
expandable.setExpanded(false);
// 更新UI
notifyDataSetChanged();
return subItemCount;
}
// 递归折叠所有子item
private int recursiveCollapse(@IntRange(from = 0) int position) {
T item = getItem(position);
IExpandable expandable = (IExpandable) item;
List<T> collapseList = new ArrayList<>();
for (int i = position + 1, n = mData.size(); i < n; i++) {
itemTemp = mData.get(i);
collapseList.add(itemTemp);
}
// 从数据源移除所有子item
mData.removeAll(collapseList);
return collapseList.size();
}
```
其中IExpandable接口如下,AbstractExpandableItem就实现了此接口:
```java
// 如果一个item是可以展开折叠类型的,就实现这个接口
public interface IExpandable<T> {
boolean isExpanded();
void setExpanded(boolean expanded);
List<T> getSubItems();
int getLevel();
}
```
2、展开源码
```java
public int expand(@IntRange(from = 0) int position, boolean animate, boolean shouldNotify) {
// 获取item实例
IExpandable expandable = getExpandableItem(position);
// 没有子item,直接设置为已展开状态,并更新UI
if (!hasSubItems(expandable)) {
expandable.setExpanded(true);
notifyItemChanged(position);
return 0;
}
// 展开item
if (!expandable.isExpanded()) {
List list = expandable.getSubItems();
mData.addAll(position + 1, list);
// 递归展开子item
subItemCount += recursiveExpand(position + 1, list);
// 设置展开状态
expandable.setExpanded(true);
}
notifyDataSetChanged();
return subItemCount;
}
```
展开状态的源码逻辑和折叠逻辑基本一致。
# 添加多类型item
```java
public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem> {
public MultipleItemQuickAdapter(List data) {
super(data);
addItemType(MultipleItem.TEXT, R.layout.text_view);
addItemType(MultipleItem.IMG, R.layout.image_view);
}
@Override
protected void convert(BaseViewHolder helper, MultipleItem item) {
switch (helper.getItemViewType()) {
case MultipleItem.TEXT:
helper.setImageUrl(R.id.tv, item.getContent());
break;
case MultipleItem.IMG:
helper.setImageUrl(R.id.iv, item.getContent());
break;
default:
break;
}
}
}
```
## 添加多类型item源码分析
在Adapter的构造方法中添加需要的itemType,源码如下:
```java
public abstract class BaseMultiItemQuickAdapter {
// 缓存各种类型的布局资源文件
private SparseIntArray layouts;
protected void addItemType(int type, @LayoutRes int layoutResId) {
if (layouts == null) {
layouts = new SparseIntArray();
}
layouts.put(type, layoutResId);
}
private int getLayoutId(int viewType) {
return layouts.get(viewType, TYPE_NOT_FOUND);
}
}
```
getLayout方法会在Adapter的onCreateViewHolder方法中,创建ViewHolder的时候进行获取,并加载相应的布局。
同时,BaseRecyclerViewAdapterHelper还提供了另外一种实现多类型item的方案MultipleItemRvAdapter,用于解决类型过多时convert方法中业务代码臃肿的问题,具体可参考[https://github.com/chaychan/MultipleItemRvAdapter](https://github.com/chaychan/MultipleItemRvAdapter)。
# 设置空布局
```java
public void setEmptyView(View emptyView) {}
```
## 设置空布局源码分析
```java
public void setEmptyView(View emptyView) {
if (mEmptyLayout == null) {
mEmptyLayout = new FrameLayout(emptyView.getContext());
final LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
final ViewGroup.LayoutParams lp = emptyView.getLayoutParams();
if (lp != null) {
layoutParams.width = lp.width;
layoutParams.height = lp.height;
}
mEmptyLayout.setLayoutParams(layoutParams);
}
mEmptyLayout.removeAllViews();
mEmptyLayout.addView(emptyView);
notifyDataSetChanged();
}
```
代码很简单,mEmptyLayout是一个FrameLayout,首先判断是否为空,为空则创建,并把要添加的空布局添加进去即可。我们已经设置好了空布局,但是什么时候会显示呢?Adapter在创建ViewHolder的时候都是根据viewType来进行创建的,而viewType是在getItemViewType方法配置,一起来看看:
```java
public int getItemViewType(int position) {
// 只有进到这里才会显示空布局
if (getEmptyViewCount() == 1) {
boolean header = mHeadAndEmptyEnable && getHeaderLayoutCount() != 0;
switch (position) {
case 0:
if (header) {
return HEADER_VIEW;
} else {
return EMPTY_VIEW;
}
case 1:
if (header) {
return EMPTY_VIEW;
} else {
return FOOTER_VIEW;
}
case 2:
return FOOTER_VIEW;
default:
return EMPTY_VIEW;
}
}
//...
}
public int getEmptyViewCount() {
if (mEmptyLayout == null || mEmptyLayout.getChildCount() == 0) {
return 0;
}
if (!mIsUseEmpty) {
return 0;
}
if (mData.size() != 0) {
return 0;
}
return 1;
}
```
可以看到,只有getEmptyViewCount返回值为1才会显示空布局,而只有mEmptyLayout不为空且有设置空布局,有配置使用空布局,数据源为空,这几个条件全部满足时,才会显示空布局(会结合HeadView一起判断,此处暂不分析)。
# 总结
BaseRecyclerViewAdapterHelper对于我们使用RecyclerView常用的功能进行了封装,主要思路为
* 找出重复代码,抽取到基类,非复用部分使用抽象方法代替,具体子类来实现
* 灵活使用RecyclerView的itemViewType特性,根据不同的viewType来创建不同的视图,如下拉上拉、空布局等等都是使用此思路实现
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路