Brvah(BaseQuickAdapter) 2.x 升级 3.x 注意事项

1,270 阅读4分钟

由于项目需要移除 support 库,需要将 Bravh 从 2.x 迁移到 3.x ,废话不多说直接看差异

导包地址差异

Bravh 2.x

com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50

Bravh 3.x

io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.14

代码差异

BaseViewHolder

  • helper.setGone(id, isGone) isGone 参数含义与2.x相反 ,true 为不可见,false 为可见

  • 2.x setBackgroundRes 替换成 setBackgroundResource

  • 2.x setNestView 移除,这个后面点击事件会提到

还有很多移除的 Api,等待你们自己去发现懒得写了。也给大家来了个处理差异的方案文章最后自取

MultipleItemRvAdapter

2.x 版本的 MultipleItemRvAdapter 3.x 上替换为 BaseProviderMultiAdapter。这个地方有点骚,新版本默认使用的 BaseViewHolder。唉,setGone 刚好相反还不让你自定义,你气不气 。莫名又奖励了一堆 bug

给大家来了个处理差异的方案文章最后自取

BaseSectionQuickAdapter

2.x 版本 BaseSectionQuickAdapter 和 3.x 版本上的差异如下

2.x 版本
public BaseSectionQuickAdapter(int layoutResId, int sectionHeadResId, List<T> data) {  
    super(layoutResId, data);  
    this.mSectionHeadResId = sectionHeadResId;  
}

3.x 版本
abstract class BaseSectionQuickAdapter<T : SectionEntity, VH : BaseViewHolder>  
    @JvmOverloads constructor(@LayoutRes private val sectionHeadResId: Int,  
    data: MutableList<T>? = null)  
    : BaseMultiItemQuickAdapter<T, VH>(data) {  
  
    constructor(
        @LayoutRes sectionHeadResId: Int, 
        @LayoutRes layoutResId: Int, 
        data: MutableList<T>? = null
    ) : this(sectionHeadResId, data) {  
        setNormalLayout(layoutResId)  
    }
}

注意:sectionHeadResIdlayoutResId。我不懂,但是我大受震撼。

Adapter 点击事件

2.x 版本
holder.addOnClickListener(viewId)
holder.addOnLongClickListener(viewId)

3.x 版本,初始化的时候即可调用 setNestView 需要自己处理下
addChildClickViewIds(viewIds)  
addChildLongClickViewIds(viewIds)

这边有一个坑点:文章写完后三天发现的
MultipleItemRvAdapter 这个 Adapter 的子控件点击事件有点特殊

设置了 Adapter 的 setOnChildViewClickListener 需要 addChildClickViewIds(viewIds) 需要写在 adapter 中才会响应,写在 BaseItemProvider 中不生效。

如果没有设置 setOnChildViewClickListener 并且 addChildClickViewIds(viewIds) 写在了 BaseItemProvider 中会回调 ItemProvider 的这个方法

override fun onChildClick(helper: BaseDefViewHolder, view: View, data: T, position: Int) {  
    super.onChildClick(helper, view, data, position)  
}

Adapter 加载更多

2.x 加载更多在 3.x BaseQuickAdapter 需要实现 LoadMoreModule 接口,然后外面监听需如下改动

adapter.getLoadMoreModule().setOnLoadMoreListener(new OnLoadMoreListener() {
        @Override
        public void onLoadMore() {
            page++;
            getDataBatch();
        }
    });
adapter.getLoadMoreModule().setAutoLoadMore(true);
// 当自动加载开启,同时数据不满一屏时,是否继续执行自动加载更多(默认为true)
adapter.getLoadMoreModule().setEnableLoadMoreIfNotFullPage(false);


smart_refresh.setOnRefreshListener(new OnRefreshListener() {
            @Override
     public void onRefresh(@NonNull RefreshLayout refreshLayout) {
          // 这里的作用是防止下拉刷新的时候还可以上拉加载
          adapter.getLoadMoreModule().setEnableLoadMore(false);
          page = 1;
          getDataBatch();
     }
});


if (pageInfo.isFirstPage()) {
                    //如果是加载的第一页数据,用 setData()
     adapter.setList(data);
} else {
                    //不是第一页,则用add
     adapter.addData(data);
}

if (data.size() < PAGE_SIZE) {
     // 如果不够一页,显示没有更多数据布局
     adapter.getLoadMoreModule().loadMoreEnd();
} else {
     adapter.getLoadMoreModule().loadMoreComplete();
}

注意:Adapter 需要实现 LoadMoreModule 接口,否则会崩溃。这边后面有 kt 的扩展有需要的可以拿走

Adapter 拖拽功能

2.x BaseItemDraggableAdapter 在 3.x 上使用 BaseSectionQuickAdapter 并且 Adapter 需要实现 DraggableModule,以前内置的拖拽回调需要实现 OnItemDragListener 接口

3.x 版本调用代码如下

adapter.draggableModule.run {  
    // 是否支持拽功能
    isDragEnabled = true  
    // 支持拖拽指定控件 ID
    toggleViewId = R.id.iv_options 
    // 这个必须在 toggleViewId 之后调用
    isDragOnLongPressEnabled = true  
}

其他

自己发现去吧,我只用到这么多

差异调整方案

加载更多 & upFetch 扩展
@JvmOverloads
fun BaseQuickAdapter<*, *>?.initLoadMore(view: BaseLoadMoreView? = null, loadMoreCall: OnLoadMoreListener) {
    checkIsLoadMoreAdapter {
        isAutoLoadMore = true
        view?.let {
            loadMoreView = it
        }
        isEnableLoadMore = true
        setOnLoadMoreListener {
            loadMoreCall.onLoadMore()
        }
    }
}

@JvmOverloads
fun BaseQuickAdapter<*, *>?.initUpFetch(enable: Boolean = true, position: Int, listener: OnUpFetchListener?) {
    checkIsUpFetchAdapter {
        isUpFetchEnable = enable
        startUpFetchPosition = position
        setOnUpFetchListener(listener)
    }
}
@JvmOverloads
fun BaseQuickAdapter<*, *>?.loadMoreEnd(enable: Boolean? = null) {
    checkIsLoadMoreAdapter {
        loadMoreEnd()
        enable?.let {
            loadMoreEnable(enable)
        }
    }
}

fun BaseQuickAdapter<*, *>?.loadMoreComplete() {
    checkIsLoadMoreAdapter {
        loadMoreComplete()
    }
}

fun BaseQuickAdapter<*, *>?.loadMoreFail() {
    checkIsLoadMoreAdapter {
        loadMoreFail()
    }
}

fun BaseQuickAdapter<*, *>?.loadMoreEnable(enable: Boolean) {
    checkIsLoadMoreAdapter {
        isEnableLoadMore = enable
    }
}

fun BaseQuickAdapter<*, *>?.upFetchEnable(enable: Boolean) {
    checkIsUpFetchAdapter {
        isUpFetchEnable = enable
    }
}

fun BaseQuickAdapter<*, *>?.checkIsLoadMoreAdapter(callback: BaseLoadMoreModule.() -> Unit) {
    (this as? LoadMoreModule)?.let {
        callback.invoke(loadMoreModule)
    } ?: if (BuildConfig.DEBUG) {
        ToastHelper.showToastLong(applicationContext, "请让 adapter 实现 LoadMore 接口 $this")
    }
}
// 对了这边还有个 UpFetch 的扩展看看就知道咋改了,不写了累了
fun BaseQuickAdapter<*, *>?.checkIsUpFetchAdapter(callback: BaseUpFetchModule.() -> Unit) {
    (this as? UpFetchModule)?.let {
        callback.invoke(upFetchModule)
    } ?: if (BuildConfig.DEBUG) {
        ToastHelper.showToastShort(applicationContext, "请让 adapter 实现 LoadMore 接口")
    }
}
自定义 BaseDefViewHolder 解决 BaseViewHolder 差异问题
open class BaseDefViewHolder(view: View) : BaseViewHolder(view) {  
    override fun setGone(viewId: Int, isGone: Boolean): BaseViewHolder {  
        return super.setGone(viewId, !isGone)  
    }  
}

fun setBackgroundRes(@IdRes viewId: Int, @DrawableRes backgroundRes: Int): BaseViewHolder {  
    setBackgroundResource(viewId, backgroundRes)  
    return this  
}
有啥差异自己补一下吧,看看你需要哪些,全局替换 BaseViewHolder
MultipleItemRvAdapter 解决方案,不需要的后面不用看了没鸟用

BaseCommonItemProvider.kt

abstract class BaseCommonItemProvider<T, K : BaseViewHolder> {  
  
lateinit var context: Context  
  
private var weakAdapter: WeakReference<BaseCommonProviderMultiAdapter<T, K>>? = null  
private val clickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }  
private val longClickViewIds by lazy(LazyThreadSafetyMode.NONE) { ArrayList<Int>() }  
  
internal fun setAdapter(adapter: BaseCommonProviderMultiAdapter<T, K>) {  
    weakAdapter = WeakReference(adapter)  
}  
  
open fun getAdapter(): BaseCommonProviderMultiAdapter<T, K>? {  
    return weakAdapter?.get()  
}  
  
abstract val itemViewType: Int  
  
abstract val layoutId: Int  
    @LayoutRes  
    get  
  
abstract fun convert(helper: K, item: T)  
  
open fun convert(helper: K, item: T, payloads: List<Any>) {}  
  
/**  
* (可选重写)创建 ViewHolder。  
* 默认实现返回[BaseViewHolder],可重写返回自定义 ViewHolder  
*  
* @param parent  
*/  
abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): K  
  
/**  
* (可选重写)ViewHolder创建完毕以后的回掉方法。  
* @param viewHolder VH  
*/  
open fun onViewHolderCreated(viewHolder: BaseViewHolder, viewType: Int) {}  
  
/**  
* Called when a view created by this [BaseItemProvider] has been attached to a window.  
* 当此[BaseItemProvider]出现在屏幕上的时候,会调用此方法  
*  
* This can be used as a reasonable signal that the view is about to be seen  
* by the user. If the [BaseItemProvider] previously freed any resources in  
* [onViewDetachedFromWindow][.onViewDetachedFromWindow]  
* those resources should be restored here.  
*  
* @param holder Holder of the view being attached  
*/  
open fun onViewAttachedToWindow(holder: BaseViewHolder) {}  
  
/**  
* Called when a view created by this [BaseItemProvider] has been detached from its  
* window.  
* 当此[BaseItemProvider]从屏幕上移除的时候,会调用此方法  
*  
* Becoming detached from the window is not necessarily a permanent condition;  
* the consumer of an Adapter's views may choose to cache views offscreen while they  
* are not visible, attaching and detaching them as appropriate.  
*  
* @param holder Holder of the view being detached  
*/  
open fun onViewDetachedFromWindow(holder: BaseViewHolder) {}  
  
/**  
* item 若想实现条目点击事件则重写该方法  
* @param helper VH  
* @param data T  
* @param position Int  
*/  
open fun onClick(helper: K, view: View, data: T, position: Int) {}  
  
/**  
* item 若想实现条目长按事件则重写该方法  
* @param helper VH  
* @param data T  
* @param position Int  
* @return Boolean  
*/  
open fun onLongClick(helper: K, view: View, data: T, position: Int): Boolean {  
    return false  
}  
  
open fun onChildClick(helper: K, view: View, data: T, position: Int) {}  
  
open fun onChildLongClick(helper: K, view: View, data: T, position: Int): Boolean {  
    return false  
}  
  
fun addChildClickViewIds(@IdRes vararg ids: Int) {  
    ids.forEach {  
        this.clickViewIds.add(it)  
    }  
}  
  
fun getChildClickViewIds() = this.clickViewIds  
  
fun addChildLongClickViewIds(@IdRes vararg ids: Int) {  
    ids.forEach {  
        this.longClickViewIds.add(it)  
    }  
}  
  
fun getChildLongClickViewIds() = this.longClickViewIds  
}
BaseCommonProviderMultiAdapter.kt
abstract class BaseCommonProviderMultiAdapter<T, K : BaseViewHolder>(data: MutableList<T>? = null) :
    BaseQuickAdapter<T, K>(0, data) {

    private val mItemProviders by lazy(LazyThreadSafetyMode.NONE) { SparseArray<BaseCommonItemProvider<T, K>>() }

    /**
     * 返回 item 类型
     * @param data List<T>
     * @param position Int
     * @return Int
     */
    protected abstract fun getItemType(data: List<T>, position: Int): Int

    /**
     * 必须通过此方法,添加 provider
     * @param provider BaseItemProvider
     */
    open fun addItemProvider(provider: BaseCommonItemProvider<T, K>) {
        provider.setAdapter(this)
        mItemProviders.put(provider.itemViewType, provider)
    }

    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): K {
        val provider = getItemProvider(viewType)
        checkNotNull(provider) { "ViewType: $viewType no such provider found,please use addItemProvider() first!" }
        provider.context = parent.context
        return provider.onCreateViewHolder(parent, viewType).apply {
            provider.onViewHolderCreated(this, viewType)
        }
    }

    override fun getDefItemViewType(position: Int): Int {
        return getItemType(data, position)
    }

    override fun convert(holder: K, item: T) {
        getItemProvider(holder.itemViewType)!!.convert(holder, item)
    }

    override fun convert(holder: K, item: T, payloads: List<Any>) {
        getItemProvider(holder.itemViewType)!!.convert(holder, item, payloads)
    }

    override fun bindViewClickListener(viewHolder: K, viewType: Int) {
        super.bindViewClickListener(viewHolder, viewType)
        bindClick(viewHolder)
        bindChildClick(viewHolder, viewType)
    }

    /**
     * 通过 ViewType 获取 BaseItemProvider
     * 例如:如果ViewType经过特殊处理,可以重写此方法,获取正确的Provider
     * (比如 ViewType 通过位运算进行的组合的)
     *
     * @param viewType Int
     * @return BaseItemProvider
     */
    protected open fun getItemProvider(viewType: Int): BaseCommonItemProvider<T, K>? {
        return mItemProviders.get(viewType)
    }

    override fun onViewAttachedToWindow(holder: K) {
        super.onViewAttachedToWindow(holder)
        getItemProvider(holder.itemViewType)?.onViewAttachedToWindow(holder)
    }

    override fun onViewDetachedFromWindow(holder: K) {
        super.onViewDetachedFromWindow(holder)
        getItemProvider(holder.itemViewType)?.onViewDetachedFromWindow(holder)
    }

    protected open fun bindClick(viewHolder: K) {
        if (getOnItemClickListener() == null) {
            //如果没有设置点击监听,则回调给 itemProvider
            //Callback to itemProvider if no click listener is set
            viewHolder.itemView.setOnClickListener {
                var position = viewHolder.bindingAdapterPosition
                if (position == RecyclerView.NO_POSITION) {
                    return@setOnClickListener
                }
                position -= headerLayoutCount

                val itemViewType = viewHolder.itemViewType
                val provider = mItemProviders.get(itemViewType)

                provider.onClick(viewHolder, it, data[position], position)
            }
        }
        if (getOnItemLongClickListener() == null) {
            //如果没有设置长按监听,则回调给itemProvider
            // If you do not set a long press listener, callback to the itemProvider
            viewHolder.itemView.setOnLongClickListener {
                var position = viewHolder.bindingAdapterPosition
                if (position == RecyclerView.NO_POSITION) {
                    return@setOnLongClickListener false
                }
                position -= headerLayoutCount

                val itemViewType = viewHolder.itemViewType
                val provider = mItemProviders.get(itemViewType)
                provider.onLongClick(viewHolder, it, data[position], position)
            }
        }
    }

    protected open fun bindChildClick(viewHolder: K, viewType: Int) {
        if (getOnItemChildClickListener() == null) {
            val provider = getItemProvider(viewType) ?: return
            val ids = provider.getChildClickViewIds()
            ids.forEach { id ->
                viewHolder.itemView.findViewById<View>(id)?.let {
                    if (!it.isClickable) {
                        it.isClickable = true
                    }
                    it.setOnClickListener { v ->
                        var position: Int = viewHolder.bindingAdapterPosition
                        if (position == RecyclerView.NO_POSITION) {
                            return@setOnClickListener
                        }
                        position -= headerLayoutCount
                        provider.onChildClick(viewHolder, v, data[position], position)
                    }
                }
            }
        }
        if (getOnItemChildLongClickListener() == null) {
            val provider = getItemProvider(viewType) ?: return
            val ids = provider.getChildLongClickViewIds()
            ids.forEach { id ->
                viewHolder.itemView.findViewById<View>(id)?.let {
                    if (!it.isLongClickable) {
                        it.isLongClickable = true
                    }
                    it.setOnLongClickListener { v ->
                        var position: Int = viewHolder.bindingAdapterPosition
                        if (position == RecyclerView.NO_POSITION) {
                            return@setOnLongClickListener false
                        }
                        position -= headerLayoutCount
                        provider.onChildLongClick(viewHolder, v, data[position], position)
                    }
                }
            }
        }
    }
}

BaseDefItemProvider.kt

abstract class BaseDefItemProvider<T> : BaseCommonItemProvider<T, BaseDefViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseDefViewHolder {
        return BaseDefViewHolder(parent.getItemView(layoutId))
    }
}

BaseDefProviderMultiAdapter.kt

/**
 * 由于 brvah 2.xx 迁移到 3.xx 部分 api 出现改动
 * 例如: [com.chad.library.adapter.base.viewholder.BaseViewHolder] 中 setGone 方法和以前逻辑相反,导致所有调用处出现异常等等。
 *
 * [BaseDefViewHolder] 重写了 [com.chad.library.adapter.base.viewholder.BaseViewHolder] 处理 api 升级后导致的改动
 *
 * 提供 [BaseCommonProviderMultiAdapter] 支持自定义 BaseViewHolder
 * 提供 [BaseDefProviderMultiAdapter] 默认使用 [BaseDefViewHolder] 来兼容新 api 的改动
 */
abstract class BaseDefProviderMultiAdapter<T>(data: MutableList<T>? = null) : BaseCommonProviderMultiAdapter<T, BaseDefViewHolder>(data)

原先继承 MultipleItemRvAdapter 改成 BaseCommonProviderMultiAdapter 即可。

继承 BaseItemProvider 改成 BaseDefItemProvider 就可以了。

不会因为 BaseViewHolder 的 Api 改动出问题了