-
概述
在使用RecyclerView时,我们经常配合着封装的Adapter第三方框架来使用,最经典的就是BaseQuickAdapter系列,它的内部封装了一些关于Adapter的通用逻辑,使得我们可以专注于业务逻辑的开发,极大地提高了工作效率。通过它也可以很方便的结合分页嵌入数据提前加载更多、刷新、多布局等操作。本文通过它的源码,在结合RecyclerView的原理,来研究一下它是怎么封装的。
-
BaseQuickAdapter
BaseQuickAdapter是这套框架的基础,我们先来看一下他是怎么实现的。
-
onCreateViewHolder方法
通过RecyclerView的原理我们知道,ViewHolder创建是在onCreateViewHolder方法中完成的,我们继承RecyclerView.Adapter的时候需要重写这个方法,BaseQuickAdapter也实现了这个方法:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val baseViewHolder: VH when (viewType) { LOAD_MORE_VIEW -> { val view = mLoadMoreModule!!.loadMoreView.getRootView(parent) baseViewHolder = createBaseViewHolder(view) mLoadMoreModule!!.setupViewHolder(baseViewHolder) } HEADER_VIEW -> { val headerLayoutVp: ViewParent? = mHeaderLayout.parent if (headerLayoutVp is ViewGroup) { headerLayoutVp.removeView(mHeaderLayout) } baseViewHolder = createBaseViewHolder(mHeaderLayout) } EMPTY_VIEW -> { val emptyLayoutVp: ViewParent? = mEmptyLayout.parent if (emptyLayoutVp is ViewGroup) { emptyLayoutVp.removeView(mEmptyLayout) } baseViewHolder = createBaseViewHolder(mEmptyLayout) } FOOTER_VIEW -> { val footerLayoutVp: ViewParent? = mFooterLayout.parent if (footerLayoutVp is ViewGroup) { footerLayoutVp.removeView(mFooterLayout) } baseViewHolder = createBaseViewHolder(mFooterLayout) } else -> { val viewHolder = onCreateDefViewHolder(parent, viewType) bindViewClickListener(viewHolder, viewType) mDraggableModule?.initView(viewHolder) onItemViewHolderCreated(viewHolder, viewType) baseViewHolder = viewHolder } } return baseViewHolder }
由此可知,BaseQuickAdapter会提供几种类型的View,一般的开发中都是单一布局,所以几乎用不到viewType,而这里,BaseQuickAdapter使用了viewType来创建不同的ViewHolder。
createBaseViewHolder会使用反射创建对应的ViewHolder:
protected open fun createBaseViewHolder(view: View): VH { var temp: Class<*>? = javaClass var z: Class<*>? = null while (z == null && null != temp) { z = getInstancedGenericKClass(temp) temp = temp.superclass } // 泛型擦除会导致z为null val vh: VH? = if (z == null) { BaseViewHolder(view) as VH } else { createBaseGenericKInstance(z, view) } return vh ?: BaseViewHolder(view) as VH } /** * get generic parameter VH * * @param z * @return */ private fun getInstancedGenericKClass(z: Class<*>): Class<*>? { try { val type = z.genericSuperclass if (type is ParameterizedType) { val types = type.actualTypeArguments for (temp in types) { if (temp is Class<*>) { if (BaseViewHolder::class.java.isAssignableFrom(temp)) { return temp } } else if (temp is ParameterizedType) { val rawType = temp.rawType if (rawType is Class<*> && BaseViewHolder::class.java.isAssignableFrom(rawType)) { return rawType } } } } } catch (e: java.lang.reflect.GenericSignatureFormatError) { e.printStackTrace() } catch (e: TypeNotPresentException) { e.printStackTrace() } catch (e: java.lang.reflect.MalformedParameterizedTypeException) { e.printStackTrace() } return null } /** * try to create Generic VH instance * * @param z * @param view * @return */ @Suppress("UNCHECKED_CAST") private fun createBaseGenericKInstance(z: Class<*>, view: View): VH? { try { val constructor: Constructor<*> // inner and unstatic class return if (z.isMemberClass && !Modifier.isStatic(z.modifiers)) { constructor = z.getDeclaredConstructor(javaClass, View::class.java) constructor.isAccessible = true constructor.newInstance(this, view) as VH } else { constructor = z.getDeclaredConstructor(View::class.java) constructor.isAccessible = true constructor.newInstance(view) as VH } } catch (e: NoSuchMethodException) { e.printStackTrace() } catch (e: IllegalAccessException) { e.printStackTrace() } catch (e: InstantiationException) { e.printStackTrace() } catch (e: InvocationTargetException) { e.printStackTrace() } return null }
-
onBindViewHolder方法
根据RecyclerView的原理我们再去onBindViewHolder方法中看看数据是如何绑定的:
override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) { if (payloads.isEmpty()) { onBindViewHolder(holder, position) return } //Add up fetch logic, almost like load more, but simpler. mUpFetchModule?.autoUpFetch(position) //Do not move position, need to change before LoadMoreView binding mLoadMoreModule?.autoLoadMore(position) when (holder.itemViewType) { LOAD_MORE_VIEW -> { mLoadMoreModule?.let { it.loadMoreView.convert(holder, position, it.loadMoreStatus) } } HEADER_VIEW, EMPTY_VIEW, FOOTER_VIEW -> return else -> convert(holder, getItem(position - headerLayoutCount), payloads) } }
这个方法会有两个重载方法,多出来的payloads表示额外的数据,两个参数的onBindViewHolder方法基本一样,针对RecyclerView.Adapter的两个重载onBindViewHolder方法,这里调用的是重载的convert方法,所以我们只需要在自定义Adapter中重写convert方法即可完成数据绑定。
-
设置Item中需要点击的子View Id
在onCreateViewHolder中else分支中调用了bindViewClickListener方法,这表示为用户Item添加事件监听:
protected open fun bindViewClickListener(viewHolder: VH, viewType: Int) { mOnItemClickListener?.let { viewHolder.itemView.setOnClickListener { v -> var position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnClickListener } position -= headerLayoutCount setOnItemClick(v, position) } } mOnItemLongClickListener?.let { viewHolder.itemView.setOnLongClickListener { v -> var position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnLongClickListener false } position -= headerLayoutCount setOnItemLongClick(v, position) } } //下面是给Item中的子View添加事件监听(如果需要的话) mOnItemChildClickListener?.let { for (id in getChildClickViewIds()) { viewHolder.itemView.findViewById<View>(id)?.let { childView -> if (!childView.isClickable) { childView.isClickable = true } childView.setOnClickListener { v -> var position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnClickListener } position -= headerLayoutCount setOnItemChildClick(v, position) } } } } mOnItemChildLongClickListener?.let { for (id in getChildLongClickViewIds()) { viewHolder.itemView.findViewById<View>(id)?.let { childView -> if (!childView.isLongClickable) { childView.isLongClickable = true } childView.setOnLongClickListener { v -> var position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) { return@setOnLongClickListener false } position -= headerLayoutCount setOnItemChildLongClick(v, position) } } } } }
这里特别的一点是可以给Item中的子View添加事件监听,不过要事先通过addChildClickViewIds方法把需要监听的子View的id添加进去:
fun addChildClickViewIds(@IdRes vararg viewIds: Int) { for (viewId in viewIds) { childClickViewIds.add(viewId) } }
-
Header和Footer
我们知道,RecyclerView是没有自带header和footer的相关API的,但是这一切完全都可以通过viewType和position来完成,看一下这里是怎么做的,我们以header为例(footer的原理一样)。
BaseQuickAdapter中有一个mHeaderLayout来盛放所有的header view,通过addHeaderView添加、通过setHeaderView替换,setHeaderView中如果mHeaderLayout之前为空(即之前没有Header)的话也会走addHeaderView方法添加header view,所以我们来看一下addHeaderView方法:
@JvmOverloads fun addHeaderView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int { if (!this::mHeaderLayout.isInitialized) { mHeaderLayout = LinearLayout(view.context) mHeaderLayout.orientation = orientation mHeaderLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) { RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT) } else { RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT) } } val childCount = mHeaderLayout.childCount var mIndex = index if (index < 0 || index > childCount) { mIndex = childCount } mHeaderLayout.addView(view, mIndex) if (mHeaderLayout.childCount == 1) { val position = headerViewPosition if (position != -1) { notifyItemInserted(position) } } return mIndex }
值得注意的一点是,mHeaderLayout.childCount == 1说明是第一次添加header view,此时mHeaderLayout还并没有attach到RecyclerView中,所以这里会调用notifyItemInserted方法插入mHeaderLayout,也就是说mHeaderLayout是作为某一项插入到RecyclerView中的,只是它的viewType是HEADER_VIEW,看一下它重写的getItemViewType方法:
companion object { const val HEADER_VIEW = 0x10000111 const val LOAD_MORE_VIEW = 0x10000222 const val FOOTER_VIEW = 0x10000333 const val EMPTY_VIEW = 0x10000555 }
override fun getItemViewType(position: Int): Int { if (hasEmptyView()) { val header = headerWithEmptyEnable && hasHeaderLayout() return when (position) { 0 -> if (header) { HEADER_VIEW } else { EMPTY_VIEW } 1 -> if (header) { EMPTY_VIEW } else { FOOTER_VIEW } 2 -> FOOTER_VIEW else -> EMPTY_VIEW } } val hasHeader = hasHeaderLayout() if (hasHeader && position == 0) { return HEADER_VIEW } else { var adjPosition = if (hasHeader) { position - 1 } else { position } val dataSize = data.size return if (adjPosition < dataSize) { getDefItemViewType(adjPosition) } else { adjPosition -= dataSize val numFooters = if (hasFooterLayout()) { 1 } else { 0 } if (adjPosition < numFooters) { FOOTER_VIEW } else { LOAD_MORE_VIEW } } } }
这里的getDefItemViewType方法返回的就是普通Item的view类型,这个方法默认是一样的,可以重写返回自己的ViewType。
总之,Header和Footer的原理就是通过设置不同的ViewType来达到区别的目的的。
-
设置spanSize
在GridLayoutManager中会有行列的占比设置,这部分封装BaseQuickAdapter是通过重写onViewAttachedToWindow方法和onAttachedToRecyclerView方法实现的:
override fun onViewAttachedToWindow(holder: VH) { super.onViewAttachedToWindow(holder) val type = holder.itemViewType if (isFixedViewType(type)) { setFullSpan(holder) } else { addAnimation(holder) } } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) weakRecyclerView = WeakReference(recyclerView) mRecyclerView = recyclerView this.context = recyclerView.context mDraggableModule?.attachToRecyclerView(recyclerView) val manager = recyclerView.layoutManager if (manager is GridLayoutManager) { val defSpanSizeLookup = manager.spanSizeLookup manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { val type = getItemViewType(position) if (type == HEADER_VIEW && headerViewAsFlow) { return 1 } if (type == FOOTER_VIEW && footerViewAsFlow) { return 1 } return if (mSpanSizeLookup == null) { if (isFixedViewType(type)) manager.spanCount else defSpanSizeLookup.getSpanSize(position) } else { if (isFixedViewType(type)) manager.spanCount else mSpanSizeLookup!!.getSpanSize(manager, type, position - headerLayoutCount) } } } } }
这里还调用了addAnimation方法设置item初始显示动画:
private fun addAnimation(holder: RecyclerView.ViewHolder) { if (animationEnable) { if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) { val animation: BaseAnimation = adapterAnimation ?: AlphaInAnimation() animation.animators(holder.itemView).forEach { startAnim(it, holder.layoutPosition) } mLastPosition = holder.layoutPosition } } }
class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : BaseAnimation { override fun animators(view: View): Array<Animator> { val animator = ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f) animator.duration = 300L animator.interpolator = LinearInterpolator() return arrayOf(animator) } companion object { private const val DEFAULT_ALPHA_FROM = 0f } }
可见默认显示动画是300毫秒内渐显,可以通过setAdapterAnimation方法设置别的动画。
-
上滑加载、下滑加载和拖动事务
前面我们看到,在onCreateViewHolder和onBindViewHolder方法中用到了三个“Module”:mLoadMoreModule、mUpFetchModule、mDraggableModule,它们在BaseQuickAdapter构造方法中会尝试初始化:
init { checkModule() } /** * 检查模块 */ private fun checkModule() { if (this is LoadMoreModule) { mLoadMoreModule = this.addLoadMoreModule(this) } if (this is UpFetchModule) { mUpFetchModule = this.addUpFetchModule(this) } if (this is DraggableModule) { mDraggableModule = this.addDraggableModule(this) } }
可见,如果要使用LoadMoreModule需要自定义的Adapter继承这个接口才行,同样其他的两种也是,后面讲到其他两种时,初始化部分就不再赘述了,具体用来干什么的先不用管,现在只需要知道,要使用这三个Module,自定义的Adapter必须继承对应的接口,它们都没有需要实现的方法,只是一个空接口。
addLoadMoreModule是BaseQuickAdapterModuleImp中的方法,因为BaseQuickAdapter继承了它,所以这里使用this调用,看一下他的addLoadMoreModule方法:
fun addLoadMoreModule(baseQuickAdapter: BaseQuickAdapter<*, *>): BaseLoadMoreModule { return BaseLoadMoreModule(baseQuickAdapter) }
在这里,BaseLoadMoreModule实例会赋给mLoadMoreModule,你可以在Adapter中重写addLoadMoreModule方法来返回自己的LoadMoreModule实例,同样,其他两种Module也是。
下面我们分别来看看它们是用来干啥的。
-
LoadMoreModule
mLoadMoreModule类型是BaseLoadMoreModule,先看一下它的setupViewHolder方法:
internal fun setupViewHolder(viewHolder: BaseViewHolder) { viewHolder.itemView.setOnClickListener { if (loadMoreStatus == LoadMoreStatus.Fail) { loadMoreToLoading() } else if (loadMoreStatus == LoadMoreStatus.Complete) { loadMoreToLoading() } else if (enableLoadMoreEndClick && loadMoreStatus == LoadMoreStatus.End) { loadMoreToLoading() } } }
失败、完成或者数据全部加载完的状态时调用loadMoreToLoading方法:
fun loadMoreToLoading() { if (loadMoreStatus == LoadMoreStatus.Loading) { return } loadMoreStatus = LoadMoreStatus.Loading baseQuickAdapter.notifyItemChanged(loadMoreViewPosition) invokeLoadMoreListener() }
这里会把loadMoreStatus修改为LoadMoreStatus.Loading,然后调用BaseQuickAdapter的notifyItemChanged方法更改loadMoreView的UI。
既然调用notifyItemChanged方法更改UI,说明loadMoreView之前已被添加,那是在什么时候添加的呢?
var isEnableLoadMore = false set(value) { val oldHasLoadMore = hasLoadMoreView() field = value val newHasLoadMore = hasLoadMoreView() if (oldHasLoadMore) { if (!newHasLoadMore) { baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition) } } else { if (newHasLoadMore) { loadMoreStatus = LoadMoreStatus.Complete baseQuickAdapter.notifyItemInserted(loadMoreViewPosition) } } }
可见,是在给isEnableLoadMore赋值的时候插入的,在这里调用notifyItemInserted方法后会走到onCreateViewHolder方法,然后就会调用上面的setupViewHolder方法,onCreateViewHolder方法中判断LOAD_MORE_VIEW类型的逻辑时有一句代码:
val view = mLoadMoreModule!!.loadMoreView.getRootView(parent)
默认的loadMoreView是:
var loadMoreView = LoadMoreModuleConfig.defLoadMoreView @JvmStatic var defLoadMoreView: BaseLoadMoreView = SimpleLoadMoreView()
SimpleLoadMoreView中的getRootView方法返回View:
override fun getRootView(parent: ViewGroup): View = parent.getItemView(R.layout.brvah_quick_view_load_more)
getItemView方法是添加的ViewGroup扩展函数:
fun ViewGroup.getItemView(@LayoutRes layoutResId: Int): View { return LayoutInflater.from(this.context).inflate(layoutResId, this, false) }
然后onBindViewHolder方法中会调用autoLoadMore方法:
internal fun autoLoadMore(position: Int) { if (!isAutoLoadMore) { //如果不需要自动加载更多,直接返回 return } if (!hasLoadMoreView()) { return } if (position < baseQuickAdapter.itemCount - preLoadNumber) { return } if (loadMoreStatus != LoadMoreStatus.Complete) { return } if (loadMoreStatus == LoadMoreStatus.Loading) { return } if (!mNextLoadEnable) { return } invokeLoadMoreListener() }
这里会调用invokeLoadMoreListener方法:
private fun invokeLoadMoreListener() { loadMoreStatus = LoadMoreStatus.Loading baseQuickAdapter.mRecyclerView?.let { it.post { mLoadMoreListener?.onLoadMore() } } ?: mLoadMoreListener?.onLoadMore() }
调用自添加的加载更多监听回调去加载更多数据。
数据获取后判断是否还有更多数据,如果没有了则调用mAdapter.loadMoreModule?.loadMoreEnd(true)方法隐藏加载更多view:
fun loadMoreEnd(gone: Boolean = false) { if (!hasLoadMoreView()) { return } // mNextLoadEnable = false isLoadEndMoreGone = gone loadMoreStatus = LoadMoreStatus.End if (gone) { baseQuickAdapter.notifyItemRemoved(loadMoreViewPosition) } else { baseQuickAdapter.notifyItemChanged(loadMoreViewPosition) } }
-
UpFetchModule
和LoadMoreModule原理类似,UpFetchModule是负责向上加载的,但是它比LoadMoreModule要简单多了,它没有布局,他只是负责向上加载的回调。注意,不要使用它来实现刷新操作,它并不适合。
什么情况下需要它呢,比如,要显示一些类目,默认显示当天的,向上滑动的时候我想要看见今天之前的类目的时候就用到它了。
BaseQuickAdapter中每次调用onBindViewHolder方法时都会调用autoUpFetch方法尝试向上加载:
internal fun autoUpFetch(position: Int) { if (!isUpFetchEnable || isUpFetching) { return } if (position <= startUpFetchPosition) { mUpFetchListener?.onUpFetch() } }
可见是根据isUpFetchEnable和isUpFetching来决定的,前者是决定是否开启自动向上加载,后者是避免重复加载。
开启自动向上加载(默认是关闭的):
private fun setUpFetch() { mAdapter.upFetchModule?.isUpFetchEnable = true mAdapter.upFetchModule?.setOnUpFetchListener { mAdapter.upFetchModule?.isUpFetching = true Thread { Thread.sleep(2000) //获取数据 model.upFetchData() }.start() } }
回调:
model.upFetchData.observe(this, { mAdapter.upFetchModule?.isUpFetching = false mAdapter.addData(0, it) })
注意这里是把数据放在首位,从而达到向上加载的目的,所以作者只是提供了思路,而实际上向上加载的效果是由我们决定的。你可以在这里决定是否需要关闭向上加载,比如你只需要向上加载一次,则可在第一次后把isUpFetchEnable置为false。
-
DraggableModule
DraggableModule里面默认有两种操作,一个是拖拽移动Item,一个是右滑删除Item:
//开启拖拽移动 mAdapter.draggableModule?.isDragEnabled = true //开启右滑删除 mAdapter.draggableModule?.isSwipeEnabled = true
初始化mDraggableModule之后,在BaseQuickAdapter的onAttachedToRecyclerView方法中会调用:
mDraggableModule?.attachToRecyclerView(recyclerView)
fun attachToRecyclerView(recyclerView: RecyclerView) { itemTouchHelper.attachToRecyclerView(recyclerView) }
private fun initItemTouch() { itemTouchHelperCallback = DragAndSwipeCallback(this) itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback) }
ItemTouchHelper的attachToRecyclerView方法如下:
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { if (mRecyclerView == recyclerView) { return; // nothing to do } if (mRecyclerView != null) { destroyCallbacks(); } mRecyclerView = recyclerView; if (recyclerView != null) { final Resources resources = recyclerView.getResources(); mSwipeEscapeVelocity = resources .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity); mMaxSwipeVelocity = resources .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity); setupCallbacks(); } }
这里调用了setupCallbacks方法:
private void setupCallbacks() { ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext()); mSlop = vc.getScaledTouchSlop(); mRecyclerView.addItemDecoration(this); mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); mRecyclerView.addOnChildAttachStateChangeListener(this); startGestureDetection(); }
可以看到,这里调用了RecyclerView的addOnItemTouchListener方法:
public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) { mOnItemTouchListeners.add(listener); }
mOnItemTouchListeners在findInterceptingOnItemTouchListener方法中使用:
private boolean findInterceptingOnItemTouchListener(MotionEvent e) { int action = e.getAction(); final int listenerCount = mOnItemTouchListeners.size(); for (int i = 0; i < listenerCount; i++) { final OnItemTouchListener listener = mOnItemTouchListeners.get(i); if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { mInterceptingOnItemTouchListener = listener; return true; } } return false; }
findInterceptingOnItemTouchListener方法又在dispatchToOnItemTouchListeners中调用,dispatchToOnItemTouchListeners在onTouchEvent中调用:
@Override public boolean onTouchEvent(MotionEvent e) { ... if (dispatchToOnItemTouchListeners(e)) { cancelScroll(); return true; } ... }
可以看到,这里如果Item消费了事件则直接return。
注意dispatchToOnItemTouchListeners方法中如果是Item第一次捕获事件的话会走findInterceptingOnItemTouchListener方法,也就是最终会调用mOnItemTouchListener的onInterceptTouchEvent方法;之后再次响应事件则直接调用mOnItemTouchListener的onTouchEvent方法。
不管是onInterceptTouchEvent还是onTouchEvent,都会调用select(vh, ACTION_STATE_SWIPE)方法:
void select(@Nullable ViewHolder selected, int actionState) { if (selected == mSelected && actionState == mActionState) { return; } mDragScrollStartTimeInMs = Long.MIN_VALUE; final int prevActionState = mActionState; // prevent duplicate animations endRecoverAnimation(selected, true); mActionState = actionState; if (actionState == ACTION_STATE_DRAG) { if (selected == null) { throw new IllegalArgumentException("Must pass a ViewHolder when dragging"); } // we remove after animation is complete. this means we only elevate the last drag // child but that should perform good enough as it is very hard to start dragging a // new child before the previous one settles. mOverdrawChild = selected.itemView; addChildDrawingOrderCallback(); } int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)) - 1; boolean preventLayout = false; if (mSelected != null) { final ViewHolder prevSelected = mSelected; if (prevSelected.itemView.getParent() != null) { final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0 : swipeIfNecessary(prevSelected); releaseVelocityTracker(); // find where we should animate to final float targetTranslateX, targetTranslateY; int animationType; switch (swipeDir) { case LEFT: case RIGHT: case START: case END: targetTranslateY = 0; targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth(); break; case UP: case DOWN: targetTranslateX = 0; targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight(); break; default: targetTranslateX = 0; targetTranslateY = 0; } if (prevActionState == ACTION_STATE_DRAG) { animationType = ANIMATION_TYPE_DRAG; } else if (swipeDir > 0) { animationType = ANIMATION_TYPE_SWIPE_SUCCESS; } else { animationType = ANIMATION_TYPE_SWIPE_CANCEL; } getSelectedDxDy(mTmpPosition); final float currentTranslateX = mTmpPosition[0]; final float currentTranslateY = mTmpPosition[1]; final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType, prevActionState, currentTranslateX, currentTranslateY, targetTranslateX, targetTranslateY) { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (this.mOverridden) { return; } if (swipeDir <= 0) { // this is a drag or failed swipe. recover immediately mCallback.clearView(mRecyclerView, prevSelected); // full cleanup will happen on onDrawOver } else { // wait until remove animation is complete. mPendingCleanup.add(prevSelected.itemView); mIsPendingCleanup = true; if (swipeDir > 0) { // Animation might be ended by other animators during a layout. // We defer callback to avoid editing adapter during a layout. postDispatchSwipe(this, swipeDir); } } // removed from the list after it is drawn for the last time if (mOverdrawChild == prevSelected.itemView) { removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); } } }; final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType, targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY); rv.setDuration(duration); mRecoverAnimations.add(rv); rv.start(); preventLayout = true; } else { removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); mCallback.clearView(mRecyclerView, prevSelected); } mSelected = null; } if (selected != null) { mSelectedFlags = (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask) >> (mActionState * DIRECTION_FLAG_COUNT); mSelectedStartX = selected.itemView.getLeft(); mSelectedStartY = selected.itemView.getTop(); mSelected = selected; if (actionState == ACTION_STATE_DRAG) { mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } final ViewParent rvParent = mRecyclerView.getParent(); if (rvParent != null) { rvParent.requestDisallowInterceptTouchEvent(mSelected != null); } if (!preventLayout) { mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); } mCallback.onSelectedChanged(mSelected, mActionState); mRecyclerView.invalidate(); }
可以看到,如果actionState不是ACTION_STATE_DRAG的话就调用swipeIfNecessary方法,拿到返回值swipeDir,根据它来判断是横向swipe还是纵向swipe,在swipeIfNecessary方法中是根据RecyclerView布局方向计算的,如果是横向swipe操作则把transitionY置为0,否则置transitionX为0。
然后根据actionState来判断使用哪种动画,总共有三种,一种是拖拽动画,一种是删除成功动画(就是Item移出RecyclerView的动画),还有一种是删除失败动画(即由于滑动距离或者手势不符合移除条件时Item复原位置时的动画)。
在动画结束监听onAnimationEnd里会调用postDispatchSwipe方法:
void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) { // wait until animations are complete. mRecyclerView.post(new Runnable() { @Override public void run() { if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() && !anim.mOverridden && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) { final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator(); // if animator is running or we have other active recover animations, we try // not to call onSwiped because DefaultItemAnimator is not good at merging // animations. Instead, we wait and batch. if ((animator == null || !animator.isRunning(null)) && !hasRunningRecoverAnim()) { mCallback.onSwiped(anim.mViewHolder, swipeDir); } else { mRecyclerView.post(this); } } } }); }
DragAndSwipeCallback的onSwiped方法中:
@Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { if (!isViewCreateByAdapter(viewHolder)) { if (mDraggableModule != null) { mDraggableModule.onItemSwiped(viewHolder); } } }
BaseDraggableModule中onItemSwiped方法如下:
open fun onItemSwiped(viewHolder: RecyclerView.ViewHolder) { val pos = getViewHolderPosition(viewHolder) if (inRange(pos)) { baseQuickAdapter.data.removeAt(pos) baseQuickAdapter.notifyItemRemoved(viewHolder.adapterPosition) if (isSwipeEnabled) { mOnItemSwipeListener?.onItemSwiped(viewHolder, pos) } } }
可以看到,在动画结束后会通知RecyclerView响应数据变化。和postDispatchSwipe方法相对的是clearView方法:
@Override public void clearView(View view) { if (Build.VERSION.SDK_INT >= 21) { final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation); if (tag instanceof Float) { ViewCompat.setElevation(view, (Float) tag); } view.setTag(R.id.item_touch_helper_previous_elevation, null); } view.setTranslationX(0f); view.setTranslationY(0f); }
在不符合变动条件(移动Item或者移除Item)时调用它回到原位置。
那么是在哪里响应手指拖动的呢?
在select方法的最后会调用mRecyclerView.invalidate()方法,在RecyclerView的onDraw方法中:
@Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } }
因为ItemTouchHelper继承自RecyclerView.ItemDecoration:
public class ItemTouchHelper extends RecyclerView.ItemDecoration implements RecyclerView.OnChildAttachStateChangeListener {
而在BaseDraggableModule的attachToRecyclerView方法中调用了ItemTouchHelper的attachToRecyclerView方法,在随后的setupCallbacks方法中调用了mRecyclerView.addItemDecoration(this)方法将ItemTouchHelper添加到了mItemDecorations中。
所以这里也会调用ItemTouchHelper的onDraw方法:
void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, List<ItemTouchHelper.RecoverAnimation> recoverAnimationList, int actionState, float dX, float dY) { final int recoverAnimSize = recoverAnimationList.size(); for (int i = 0; i < recoverAnimSize; i++) { final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); anim.update(); final int count = c.save(); onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, false); c.restoreToCount(count); } if (selected != null) { final int count = c.save(); onChildDraw(c, parent, selected, dX, dY, actionState, true); c.restoreToCount(count); } }
可以看到,这里会通过animation的update方法(比如阻尼等拖动效果的实现)计算出最新的Item View的坐标信息,然后调用ItemTouchUIUtilImpl的onDraw方法:
@Override public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (Build.VERSION.SDK_INT >= 21) { if (isCurrentlyActive) { Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation); if (originalElevation == null) { originalElevation = ViewCompat.getElevation(view); float newElevation = 1f + findMaxElevation(recyclerView, view); ViewCompat.setElevation(view, newElevation); view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation); } } } //产生移动效果 view.setTranslationX(dX); view.setTranslationY(dY); }
在这里产生随手指移动的效果。
-
setDiffNewData方法
你还可以通过setDiffNewData方法来设置新的数据集,它只会更新和旧数据集相比不同的项:
open fun setDiffNewData(list: MutableList<T>?) { if (hasEmptyView()) { // If the current view is an empty view, set the new data directly without diff setNewInstance(list) return } mDiffHelper?.submitList(list) }
当然使用这个方法的前提需要调用setDiffConfig方法设置mDiffHelper:
fun setDiffConfig(config: BrvahAsyncDifferConfig<T>) { mDiffHelper = BrvahAsyncDiffer(this, config) }
主要是提供一个BrvahAsyncDifferConfig:
class BrvahAsyncDifferConfig<T>( @RestrictTo(RestrictTo.Scope.LIBRARY) val mainThreadExecutor: Executor?, val backgroundThreadExecutor: Executor, val diffCallback: DiffUtil.ItemCallback<T>)
关键是提供一个DiffUtil.ItemCallback接口,这个接口就是比较数据项的回调接口。
在mDiffHelper调用submitList方法时:
fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) { // incrementing generation means any currently-running diffs are discarded when they finish val runGeneration: Int = ++mMaxScheduledGeneration if (newList === adapter.data) { // nothing to do (Note - still had to inc generation, since may have ongoing work) commitCallback?.run() return } val oldList: List<T> = adapter.data // fast simple remove all if (newList == null) { val countRemoved: Int = adapter.data.size adapter.data = arrayListOf() // notify last, after list is updated mUpdateCallback.onRemoved(0, countRemoved) onCurrentListChanged(oldList, commitCallback) return } // fast simple first insert if (adapter.data.isEmpty()) { adapter.data = newList // notify last, after list is updated mUpdateCallback.onInserted(0, newList.size) onCurrentListChanged(oldList, commitCallback) return } config.backgroundThreadExecutor.execute { val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int { return oldList.size } override fun getNewListSize(): Int { return newList.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] return if (oldItem != null && newItem != null) { config.diffCallback.areItemsTheSame(oldItem, newItem) } else oldItem == null && newItem == null // If both items are null we consider them the same. } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] if (oldItem != null && newItem != null) { return config.diffCallback.areContentsTheSame(oldItem, newItem) } if (oldItem == null && newItem == null) { return true } throw AssertionError() } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] if (oldItem != null && newItem != null) { return config.diffCallback.getChangePayload(oldItem, newItem) } throw AssertionError() } }) mMainThreadExecutor.execute { if (mMaxScheduledGeneration == runGeneration) { latchList(newList, result, commitCallback) } } } }
可以看到,在后台线程中比较数据,在主线程中调用latchList方法更新数据:
private fun latchList( newList: MutableList<T>, diffResult: DiffResult, commitCallback: Runnable?) { val previousList: List<T> = adapter.data adapter.data = newList diffResult.dispatchUpdatesTo(mUpdateCallback) onCurrentListChanged(previousList, commitCallback) }
在DiffResult的构造方法中已经调用findMatchingItems方法,在里面会通过前面DiffUtil.Callback的相关方法产生mOldItemStatuses和mNewItemStatuses的内容,它们保存的分别是旧新数据集的下标,最后,调用diffResult.dispatchUpdatesTo方法传入的mUpdateCallback的相关方法进行数据更新:
class BrvahListUpdateCallback(private val mAdapter: BaseQuickAdapter<*, *>) : ListUpdateCallback { override fun onInserted(position: Int, count: Int) { mAdapter.notifyItemRangeInserted(position + mAdapter.headerLayoutCount, count) } override fun onRemoved(position: Int, count: Int) { if (mAdapter.mLoadMoreModule?.hasLoadMoreView() == true && mAdapter.itemCount == 0) { // 如果注册了加载更多,并且当前itemCount为0,则需要加上loadMore所占用的一行 mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count + 1) } else { mAdapter.notifyItemRangeRemoved(position + mAdapter.headerLayoutCount, count) } } override fun onMoved(fromPosition: Int, toPosition: Int) { mAdapter.notifyItemMoved(fromPosition + mAdapter.headerLayoutCount, toPosition + mAdapter.headerLayoutCount) } override fun onChanged(position: Int, count: Int, payload: Any?) { mAdapter.notifyItemRangeChanged(position + mAdapter.headerLayoutCount, count, payload) } }
如果使用默认的Executor的话,你也可以直接调用setDiffCallback方法设置DiffUtil.ItemCallback即可。
-
默认加载动画
当Item View添加到window时的显示动画是通过addAnimation方法添加的:
private fun addAnimation(holder: RecyclerView.ViewHolder) { if (animationEnable) { if (!isAnimationFirstOnly || holder.layoutPosition > mLastPosition) { val animation: BaseAnimation = adapterAnimation ?: AlphaInAnimation() animation.animators(holder.itemView).forEach { startAnim(it, holder.layoutPosition) } mLastPosition = holder.layoutPosition } } }
可以看到,默认是AlphaInAnimation。
adapterAnimation可以自己设置,也可以调用setAnimationWithDefault方法设置默认动画:
fun setAnimationWithDefault(animationType: AnimationType) { adapterAnimation = when (animationType) { AnimationType.AlphaIn -> AlphaInAnimation() AnimationType.ScaleIn -> ScaleInAnimation() AnimationType.SlideInBottom -> SlideInBottomAnimation() AnimationType.SlideInLeft -> SlideInLeftAnimation() AnimationType.SlideInRight -> SlideInRightAnimation() } }
addAnimation方法是在onViewAttachedToWindow方法中调用的:
override fun onViewAttachedToWindow(holder: VH) { super.onViewAttachedToWindow(holder) val type = holder.itemViewType if (isFixedViewType(type)) { setFullSpan(holder) } else { addAnimation(holder) } }
isFixedViewType方法会保证是数据Item才会响应动画,头布局、加载更多等Item不会响应动画。
而onViewAttachedToWindow方法又是在RecyclerView的addView流程中调用的,所以在Item添加到RecyclerView时会触发动画。
-
-
BaseMultiItemQuickAdapter
在开发中,我们有时会遇到多布局的情况,就是列表中存在不同样式的布局,这种情况下我们可以使用BaseMultiItemQuickAdapter。
BaseMultiItemQuickAdapter继承自BaseQuickAdapter,所以它的原理完全一样,我们这里只看不同的地方。
abstract class BaseMultiItemQuickAdapter<T : MultiItemEntity, VH : BaseViewHolder>(data: MutableList<T>? = null) : BaseQuickAdapter<T, VH>(0, data) { private val layouts: SparseIntArray by lazy(LazyThreadSafetyMode.NONE) { SparseIntArray() } override fun getDefItemViewType(position: Int): Int { return data[position].itemType } override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH { val layoutResId = layouts.get(viewType) require(layoutResId != 0) { "ViewType: $viewType found layoutResId,please use addItemType() first!" } return createBaseViewHolder(parent, layoutResId) } /** * 调用此方法,设置多布局 * @param type Int * @param layoutResId Int */ protected fun addItemType(type: Int, @LayoutRes layoutResId: Int) { layouts.put(type, layoutResId) } }
可以看到很少的代码,重写了getDefItemViewType和onCreateDefViewHolder方法。
我们知道,在上面的BaseQuickAdapter原理分析中,onCreateViewHolder中会调用onCreateDefViewHolder方法创建ViewHolder,在这里会通过一个layouts集合保存所有的布局id,在调用onCreateDefViewHolder的时候从中取出,从而达到显示多种布局的效果。
通过调用adapter的addItemType来根据type添加多个布局id,比如:
init { addItemType(FlightListAdapterModel.TUIJIAN, R.layout.layout_001) addItemType(FlightListAdapterModel.FLIGHT, R.layout.layout_002) }
此外,注意,每一个Item Data都必须继承自MultiItemEntity。
然后convert方法中根据itemType来实现不同操作,比如:
override fun convert(helper: BaseViewHolder, item: FlightlistHandleDataBean.Flight) { when (helper.itemViewType) { FlightListAdapterModel.TUIJIAN -> onBindHolderTuijian(helper, item) FlightListAdapterModel.FLIGHT -> onBindHolderFlight(helper, item) } }
-
其他
此外,还有基于BaseQuickAdapter的几种适用不同情况的Adapter,下面给出注释,在对应情况下可以使用。
/** * 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 ItemProvider 中。 * 适用于以下情况: * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[getItemType]中返回对应类型 * 2、item 类型较多,在convert()中管理起来复杂 * * ViewHolder 由 [BaseItemProvider] 实现,并且每个[BaseItemProvider]可以拥有自己类型的ViewHolder类型。 * * @param T data 数据类型 * @constructor */ abstract class BaseProviderMultiAdapter<T>(data: MutableList<T>? = null) : BaseQuickAdapter<T, BaseViewHolder>(0, data) {
/** * 快速实现带头部的 Adapter,由于本质属于多布局,所以继承自[BaseMultiItemQuickAdapter] * @param T : SectionEntity * @param VH : BaseViewHolder * @property sectionHeadResId Int * @constructor */ abstract class BaseSectionQuickAdapter<T : SectionEntity, VH : BaseViewHolder> @JvmOverloads constructor(@LayoutRes private val sectionHeadResId: Int, data: MutableList<T>? = null) : BaseMultiItemQuickAdapter<T, VH>(data) {
/** * 多类型布局,通过代理类的方式,返回布局 id 和 item 类型; * 适用于: * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,只需要在[BaseMultiTypeDelegate.getItemType]中返回对应类型 * 2、item 类型较少 * 如果类型较多,为了方便隔离各类型的业务逻辑,推荐使用[BaseBinderAdapter] * * @param T * @param VH : BaseViewHolder * @property mMultiTypeDelegate BaseMultiTypeDelegate<T>? * @constructor */ abstract class BaseDelegateMultiAdapter<T, VH : BaseViewHolder>(data: MutableList<T>? = null) : BaseQuickAdapter<T, VH>(0, data) {
/** * 使用 Binder 来实现adapter,既可以实现单布局,也能实现多布局 * 数据实体类也不存继承问题 * * 当有多种条目的时候,避免在convert()中做太多的业务逻辑,把逻辑放在对应的 BaseItemBinder 中。 * 适用于以下情况: * 1、实体类不方便扩展,此Adapter的数据类型可以是任意类型,默认情况下不需要实现 getItemType * 2、item 类型较多,在convert()中管理起来复杂 * * ViewHolder 由 [BaseItemBinder] 实现,并且每个[BaseItemBinder]可以拥有自己类型的ViewHolder类型。 * * 数据类型为Any */ open class BaseBinderAdapter(list: MutableList<Any>? = null) : BaseQuickAdapter<Any, BaseViewHolder>(0, list) {
对RecyclerView.Adapter的封装框架解析
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 目录介绍 1.RecycleView的结构 2.Adapter2.1 RecyclerView.Adapter扮演...
- 前言 在 Android 5.0 之前,如果你需要展示一个可以滚动的列表,我们用的是 ListView 控件。An...
- RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替...