[TOC]
# 優化Adapter代碼
item view 如下:

代碼如下:
```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

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零碎問題
- 其他零碎問題
- 開發思路