BRAVH源码解读
对于BRAVH基本流程的梳理 所以不会针对每一个判断每一个功能都详细追溯 只会做大致的了解,和一些关键点的详解
BaseViewHolder
首先我们来看一下BaseViewHolder的关键代码,相当简单,可以看到主要是保存这个View,然后views
是这个条目的控件的集合,以setText()
为例,根据id
获取到控件并添加到控件集合,然后view.setText
,就酱
剩余的是一些事件点击的绑定和处理,后面会单独将事件点击这一块
public BaseViewHolder(final View view) {
super(view);
this.views = new SparseArray<>();
this.childClickViewIds = new LinkedHashSet<>();
this.itemChildLongClickViewIds = new LinkedHashSet<>();
this.nestViews = new HashSet<>();
convertView = view;
}
public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
TextView view = getView(viewId);
view.setText(value);
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;
}
BaseQuickAdapter
这里我们打算按照着Recycler的生命周期来讲解会更好理解整个流程是如何运作的
构造方法
初始化layoutId
和data
data不传就默认是一个空的ArrayList
public BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data) {
this.mData = data == null ? new ArrayList<T>() : data;
if (layoutResId != 0) {
this.mLayoutResId = layoutResId;
}
}
onAttachedToRecyclerView
Adapter与RecyclerView关联起来 这里面主要是做表格布局管理器的头布局和脚布局自占一行的适配
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = getItemViewType(position);
if (type == HEADER_VIEW && isHeaderViewAsFlow()) {
return 1;
}
if (type == FOOTER_VIEW && isFooterViewAsFlow()) {
return 1;
}
if (mSpanSizeLookup == null) {
return isFixedViewType(type) ? gridManager.getSpanCount() : 1;
} else {
return (isFixedViewType(type)) ? gridManager.getSpanCount() : mSpanSizeLookup.getSpanSize(gridManager,
position - getHeaderLayoutCount());
}
}
});
}
}
getItemCount
获取Item数量
public int getItemCount() {
int count;
if (getEmptyViewCount() == 1) {
count = 1;
if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
count++;
}
if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
count++;
}
} else {
count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
}
return count;
}
- 是空布局:
- 根据是否有头布局和脚布局来增加数量
- 没有空布局:
- 数量 = 头布局+数据条目数量 + 脚布局+加载更多布局
getItemViewType
获取Item类型
@Override
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;
}
}
int numHeaders = getHeaderLayoutCount();
if (position < numHeaders) {
return HEADER_VIEW;
} else {
int adjPosition = position - numHeaders;
int adapterCount = mData.size();
if (adjPosition < adapterCount) {
return getDefItemViewType(adjPosition);
} else {
adjPosition = adjPosition - adapterCount;
int numFooters = getFooterLayoutCount();
if (adjPosition < numFooters) {
return FOOTER_VIEW;
} else {
return LOADING_VIEW;
}
}
}
}
- 首先判断是否是空布局,如果是,则根据位置来判断是空布局、头布局、脚布局的哪一个
- 不是空布局的情况下
- 首先判断是否是头布局,是就返回头布局
- 否则,拿到条目位置,然后和数据位置做比对,如果位置和数据大小一致,则判断是否是加载布局
- 如果位置是在数据列表中则调用
getDefItemViewType()
getDefItemViewType()
protected int getDefItemViewType(int position) {
if (mMultiTypeDelegate != null) {
return mMultiTypeDelegate.getDefItemViewType(mData, position);
}
return super.getItemViewType(position);
}
中间的判断是是否启用多类型条目的,如果没启用,就直接返回默认的条目类型,就是0!!
多类型条目
现在来看一下多类型条目
MultiTypeDelegate.getDefItemViewType()
public final int getDefItemViewType(List<T> data, int position) {
T item = data.get(position);
return item != null ? getItemType(item) : DEFAULT_VIEW_TYPE;
}
可以看到,做了判断如果数据不为空就会调用MultiTypeDelegate.getItemType()
然后我们看到这个MultiTypeDelegate.getItemType()
是一个抽象方法。
这里我们要说一下多类型条目的使用了 代码如下:
public class MatchDataAdapter extends BaseQuickAdapter<MatchStatusBean.StatsBean, BaseViewHolder> {
public MatchDataAdapter() {
super(null);
setMultiTypeDelegate(new MultiTypeDelegate<MatchStatusBean.StatsBean>() {
@Override
protected int getItemType(MatchStatusBean.StatsBean statsBean) {
switch (Integer.parseInt(statsBean.type)) {
case 12:
// 比分
return C.MATCH.ITEM_TYPE_MATCH_DATA_POINT;
case 14:
//球队统计
return C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT;
case 13:
//全场最佳
return C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST;
default:
break;
}
return 0;
}
});
getMultiTypeDelegate()
.registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_POINT, R.layout.item_match_data_point)
.registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT, R.layout.item_match_data_count_list)
.registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST, R.layout.item_null);
}
@Override
protected void convert(BaseViewHolder helper, MatchStatusBean.StatsBean item) {
switch (helper.getItemViewType()) {
//比分
case C.MATCH.ITEM_TYPE_MATCH_DATA_POINT:
具体逻辑操作。。。
break;
//球队统计
case C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT:
具体逻辑操作。。。
break;
case C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST:
具体逻辑操作。。。
default:
break;
}
}
}
简单说一下
setMultiTypeDelegate()
设置一个MultiTypeDelegate
对象并实现它的getItemType()
方法,根据自己数据具体情况,来返回多类型条目的标识(这个也是上面我们分析的那个抽象方法的具体实现了)追进去看,其实就是初始化了BaseQuickAdapter
内的MultiTypeDelegate
对象,也就是我们刚刚getDefItemViewType()
中所使用的那一个
public void setMultiTypeDelegate(MultiTypeDelegate<T> multiTypeDelegate) {
mMultiTypeDelegate = multiTypeDelegate;
}
getMultiTypeDelegate().registerItemType()
:getMultiTypeDelegate
顾名思义也就是获取我们刚刚初始化的那个MultiTypeDelegate
对象,registerItemType()
这里先说一下,BRAVH提供了两种模式来注册多类型条目,并且两种模式是不能同时使用的,这就是下面checkMode()
的作用,防止你同时使用两种方式来注册多类型条目。 然后进入addItmType()
: 可以看到就是把多类型标识
和资源id
都放进了layouts
里面,而这个layouts
自然是在MultiTypeDelegate.getLayoutId()
的时候被调用 ,这里先留个概念,后面onCreateViewHolder
的时候我们会用到
public MultiTypeDelegate registerItemType(int type, @LayoutRes int layoutResId) {
selfMode = true;
checkMode(autoMode);
addItemType(type, layoutResId);
return this;
}
private void addItemType(int type, @LayoutRes int layoutResId) {
if (this.layouts == null) {
this.layouts = new SparseIntArray();
}
this.layouts.put(type, layoutResId);
}
- 最后就是在
helper.getItemViewType()
做判断来确定具体条目的类型了,因为在上面重写了MultiTypeDelegate.getItemType()
所以根据我们之前的分析MultiTypeDelegate!=null
的情况下,helper.getItemViewType()
获取到的就是我们MultiTypeDelegate.getItemType()
返回的值
onCreateViewHolder
加载ViewHolder的布局
@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
K baseViewHolder = null;
this.mContext = parent.getContext();
this.mLayoutInflater = LayoutInflater.from(mContext);
switch (viewType) {
case LOADING_VIEW:
baseViewHolder = getLoadingView(parent);
break;
case HEADER_VIEW:
baseViewHolder = createBaseViewHolder(mHeaderLayout);
break;
case EMPTY_VIEW:
baseViewHolder = createBaseViewHolder(mEmptyLayout);
break;
case FOOTER_VIEW:
baseViewHolder = createBaseViewHolder(mFooterLayout);
break;
default:
baseViewHolder = onCreateDefViewHolder(parent, viewType);
bindViewClickListener(baseViewHolder);
}
baseViewHolder.setAdapter(this);
return baseViewHolder;
}
getLoadingView()
: BRAVH给我们内置提供了简单的加载更多布局
,这个方法内部也是使用了createBaseViewHolder()
,那么我们就来看看createBaseViewHolder()
createBaseViewHolder
: 这里它主要是针对自定义ViewHolder所做的事情,我们一般使用BaseViewHolder
就足够了,所以就只需要关注最后一行,其实他就是new BaseViewHolder(view)
而已(关于BaseViewHolder
,前面我们已经大致分析了)
protected K createBaseViewHolder(View view) {
Class temp = getClass();
Class z = null;
while (z == null && null != temp) {
z = getInstancedGenericKClass(temp);
temp = temp.getSuperclass();
}
K k = createGenericKInstance(z, view);
return null != k ? k : (K) new BaseViewHolder(view);
}
onCreateDefViewHolder()
: 当不是头布局、加载布局、脚布局、空布局的情况下(也就是正常的数据布局),就会调用这个方法。进去一看,相当简单,赫然看到了MultiTypeDelegate.getLayoutId(viewType)
,也就是我们之前分析的根据条目类型
获取到具体的布局资源
,然后createBaseViewHolder()
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
int layoutId = mLayoutResId;
if (mMultiTypeDelegate != null) {
layoutId = mMultiTypeDelegate.getLayoutId(viewType);
}
return createBaseViewHolder(parent, layoutId);
}
bindViewClickListener()
: 最后就是对BaseViewHolder
的点击事件的绑定而已,就不贴代码了。
onBindViewHolder
将数据绑定到布局上,以及一些逻辑的控制就写这啦
@Override
public void onBindViewHolder(K holder, int positions) {
//Add up fetch logic, almost like load more, but simpler.
autoUpFetch(positions);
//Do not move position, need to change before LoadMoreView binding
autoLoadMore(positions);
int viewType = holder.getItemViewType();
switch (viewType) {
case 0:
convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
break;
case LOADING_VIEW:
mLoadMoreView.convert(holder);
break;
case HEADER_VIEW:
break;
case EMPTY_VIEW:
break;
case FOOTER_VIEW:
break;
default:
convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
break;
}
}
autoUpFetch()
: BRAVH提供给我们下拉加载,判断是否启用,是否达到了我们需要的加载位置,来调用我们实现的加载逻辑
private void autoUpFetch(int positions) {
if (!isUpFetchEnable() || isUpFetching()) {
return;
}
if (positions <= mStartUpFetchPosition && mUpFetchListener != null) {
mUpFetchListener.onUpFetch();
}
}
autoLoadMore()
: 上拉加载更多,根据我们的位置和加载状态来判断是否调用我们实现的加载更多的逻辑,就不细看了int viewType = holder.getItemViewType()
: 上面我们分析过getItemViewType()
,默认就是返回0,convert()
就是我们使用过程中具体实现的逻辑了。 看一眼mLoadMoreView.convert(holder)
,发现里面就是根据状态来选择布局的显示
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;
case STATUS_END:
visibleLoading(holder, false);
visibleLoadFail(holder, false);
visibleLoadEnd(holder, true);
break;
case STATUS_DEFAULT:
visibleLoading(holder, false);
visibleLoadFail(holder, false);
visibleLoadEnd(holder, false);
break;
}
}
onViewAttachedToWindow
当Item进入这个页面的时候调用
Override
public void onViewAttachedToWindow(K holder) {
super.onViewAttachedToWindow(holder);
int type = holder.getItemViewType();
if (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW) {
setFullSpan(holder);
} else {
addAnimation(holder);
}
}
setFullSpan(holder)
: 当条目类型是空布局、头布局、脚布局、加载布局的时候会触发,进来以后发现了,原来就是针对StaggeredGridLayout
做独占一行的适配
protected void setFullSpan(RecyclerView.ViewHolder holder) {
if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder
.itemView.getLayoutParams();
params.setFullSpan(true);
}
}
addAnimation()
: 顾名思义,就是条目动画的添加。进来看到,做了如下判断:是否开启动画、是否初次进入才启动动画、然后判断是否使用自定义动画,默认动画mSelectAnimation
是一个淡入动画。然后进来到真正启用动画的地方了
private void addAnimation(RecyclerView.ViewHolder holder) {
if (mOpenAnimationEnable) {
if (!mFirstOnlyEnable || holder.getLayoutPosition() > mLastPosition) {
BaseAnimation animation = null;
if (mCustomAnimation != null) {
animation = mCustomAnimation;
} else {
animation = mSelectAnimation;
}
for (Animator anim : animation.getAnimators(holder.itemView)) {
startAnim(anim, holder.getLayoutPosition());
}
mLastPosition = holder.getLayoutPosition();
}
}
}
在说动画启动之前,我们需要先知道BaseAnimation
这个接口,只有一个Animator[] getAnimators(View view)
方法,然后我们可以借鉴一下BRAVH
内置的AlphaInAnimation
,发现只是new了一个动画数组。
public class AlphaInAnimation implements BaseAnimation {
private static final float DEFAULT_ALPHA_FROM = 0f;
private final float mFrom;
public AlphaInAnimation() {
this(DEFAULT_ALPHA_FROM);
}
public AlphaInAnimation(float from) {
mFrom = from;
}
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)};
}
}
回过头来看,也就是遍历启动这个动画集合里面的每一个动画,也就是我们可以给我们的列表组合各种各样的入场动画。
for (Animator anim : animation.getAnimators(holder.itemView)) {
startAnim(anim, holder.getLayoutPosition());
}
protected void startAnim(Animator anim, int index) {
anim.setDuration(mDuration).start();
anim.setInterpolator(mInterpolator);
}
onViewDetachedFromWindow
当Item离开这个页面的时候调用
基本流程结束
来到这里证明我们的基本流程已经讲解完了 相信带着这套流程的理解,在具体功能展开窥探源码也会更加如鱼得水