🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[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来创建不同的视图,如下拉上拉、空布局等等都是使用此思路实现