[使用文档] Android 快速构建 RecyclerView, 比 BRVAH 更简单强大

Home Page: http://liangjingkanji.github.io/BRV/

License: MIT License

Java 35.01% Kotlin 64.99% recyclerview

BRV为快速构建RV列表工具, 以开源分享来完善, 将一直保持社区维护

Welcome to international translation of this project's documents/notes, thank you for your support!

欢迎贡献代码/问题

  • 开发效率No.1
  • 永远保持社区维护
  • 低代码/高扩展性
  • 优秀的源码/注释/文档/示例
  • 快速创建多类型列表
  • 一对一/一对多创建多类型
  • 添加头布局和脚布局
  • 点击(防抖动)/长按事件
  • 分组(展开折叠/递归层次/展开置顶/拖拽/侧滑/多类型/单一展开模式)
  • 悬停/粘性头部
  • 快速创建分隔线/间隔
  • 切换模式(例如切换编辑模式)
  • 选择模式(多选/单选/全选/取消全选/反选)
  • 下拉刷新(Refresh) | 上拉加载(LoadMore) | 下拉加载(UpFetch), 由 SmartRefreshLayout 实现
  • 预加载(Preload)
  • 对比数据更新(Diffs)
  • 自动分页加载数据
  • 列表动画/骨骼图动画
  • 列表缺省页, 由 StateLayout 实现
  • 支持DataBinding
  • 支持ViewBinding
  • 可添加 FlexboxLayoutManager 实现伸缩列表自动换行
  • 可添加 Net (强大的协程网络请求)实现自动化网络请求
  • Project 的 settings.gradle 添加仓库

    dependencyResolutionManagement {
        repositories {
            // ...
            maven { url 'https://jitpack.io' }
    

    Module 的 build.gradle 添加依赖框架

    dependencies {
        implementation 'com.github.liangjingkanji:BRV:1.6.0'
    

    License

    MIT License
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    ClassCastException: kotlin.collections.EmptyList cannot be cast to kotlin.collections.MutableList
    

    手机型号:小米6
    Android:9
    操作:头|脚布局-----> 添加头部

    2020-05-23 11:08:47.112 27889-27889/com.drake.brv.sample E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.drake.brv.sample, PID: 27889
    java.lang.ClassCastException: kotlin.collections.EmptyList cannot be cast to kotlin.collections.MutableList
    at com.drake.brv.BindingAdapter.addHeader(BindingAdapter.kt:300)
    at com.drake.brv.BindingAdapter.addHeader$default(BindingAdapter.kt:297)
    at com.drake.brv.sample.ui.fragment.HeaderFooterFragment$initToolbar$1.onMenuItemClick(HeaderFooterFragment.kt:74)
    at androidx.appcompat.widget.Toolbar$1.onMenuItemClick(Toolbar.java:207)
    at androidx.appcompat.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:781)
    at androidx.appcompat.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:840)
    at androidx.appcompat.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:158)
    at androidx.appcompat.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:991)
    at androidx.appcompat.view.menu.MenuPopup.onItemClick(MenuPopup.java:128)
    at android.widget.AdapterView.performItemClick(AdapterView.java:318)
    at android.widget.AbsListView.performItemClick(AbsListView.java:1198)
    at android.widget.AbsListView$PerformClick.run(AbsListView.java:3178)
    at android.widget.AbsListView$3.run(AbsListView.java:4132)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:207)
    at android.app.ActivityThread.main(ActivityThread.java:6878)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)

    BRV数据更新问题

    你好,我在viewpager2+fragment中使用BRV,recyclerView.linear().setup {
    addType(R.layout.item_task)
    }, fragment中使用相同布局、相同model、不同fragment请求数据不同,但是加载出来都是第一个fragment请求的数据。这是什么原因导致的呢。

    页面回退时候, binding.page.onRefresh() 会被执行两次

    非常好的rv库, 使用中碰到一个问题.

    当列表也点击到详情页, 再从详情页退回到列表也时候, binding.page.onRefresh() {} 会被执行两次, 回退时候没有手动执行刷新代码

    有时候用 binding.page.refresh() 刷新也会执行两次, 不知道那里出问题了

    不使用DataBinding会直接崩溃

    version:1.3.13

    java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/databinding/DataBinderMapperImpl;
    at androidx.databinding.DataBindingUtil.(DataBindingUtil.java:32)
    at androidx.databinding.DataBindingUtil.inflate(DataBindingUtil.java:95)
    at com.drake.brv.BindingAdapter.onCreateViewHolder(BindingAdapter.kt:127)
    at com.drake.brv.BindingAdapter.onCreateViewHolder(BindingAdapter.kt:67)
    at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
    at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)

    Activity And Bean

    data class TestBean(val name: String, val subset: List<TestBean>) : ItemExpand {
        override var itemGroupPosition: Int = 0
        override var itemExpand: Boolean = false
        override var itemSublist: List<Any?>? = subset
        //color 只是为了展示区分
        val color: Int
            get() = when {
                name.contains("A") -> {
                    Color.GRAY
                name.contains("B") -> {
                    Color.GREEN
                name.contains("C") -> {
                    Color.BLUE
                name.contains("D") -> {
                    Color.RED
                else -> {
                    throw IllegalArgumentException()
    class TestGroupActivity : AppCompatActivity() {
        private lateinit var adapter: BindingAdapter
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test_group)
            adapter = findViewById<RecyclerView>(R.id.rv_test).linear().setup {
                addType<TestBean>(R.layout.item_test)
                onClick(R.id.v_container) {
                    expandOrCollapse()
            initData()
        private fun initData() {
            adapter.models = mutableListOf<TestBean>().apply {
                repeat(1) { a ->
                    add(TestBean("A_$a", mutableListOf<TestBean>().apply {
                        repeat(5) { b ->
                            add(TestBean("B_$b", mutableListOf<TestBean>().apply {
                                repeat(5) { c ->
                                    add(TestBean("C_$c", mutableListOf<TestBean>().apply {
                                        repeat(5) { d ->
                                            add(TestBean("D_$d", emptyList()))
    

    activity_test_group.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_test"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never" />
    </LinearLayout>

    item_test.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
            <variable
                name="x"
                type="test.TestBean" />
        </data>
        <LinearLayout
            android:id="@+id/v_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="8dp"
                android:textColor="@{x.color}"
                android:text="@{x.name}" />
        </LinearLayout>
    </layout>
    分组列表模式下,对于嵌套分组对象是否可以增加一个字段记录当前层级(深度)
    data class ItemData(
        val name: String,
        val subset: List<ItemData>,
    ) : ItemExpand {
        // 同级别分组的索引位置
        override var itemGroupPosition: Int = 0
        // 当前条目是否展开
        override var itemExpand: Boolean = false
        // 该变量存储子列表
        override var itemSublist: List<Any?>? = emptyList()
            get() = subset
        // 新增一个能记录当前层级的字段,用于UI嵌套对象UI判断偏移位置
        var level ? depth ...
    添加函数泛型返回对应List<Type>的models
    

    fun <T> BindingAdapter.getModels() = mutable as ArrayList<T>
    获取model有getModel(),能否为getModels时也添加一个类似的方法,有时候会方便许多

    尝试使用1.3.31版本时,无法正确加载项目

    Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve com.scwang.smart:refresh-layout-kernel:2.0.3.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve com.scwang.smart:refresh-footer-classics:2.0.3.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve com.scwang.smart:refresh-footer-classics:2.0.3.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve com.scwang.smart:refresh-layout-kernel:2.0.3.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugUnitTest/compileClasspath': Could not resolve com.scwang.smart:refresh-layout-kernel:2.0.3.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugUnitTest/compileClasspath': Could not resolve com.scwang.smart:refresh-header-material:2.0.1.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve com.scwang.smart:refresh-header-material:2.0.1.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve com.scwang.smart:refresh-header-material:2.0.1.
    Show Details
    Affected Modules: app

    Unable to resolve dependency for ':app@debugUnitTest/compileClasspath': Could not resolve com.scwang.smart:refresh-footer-classics:2.0.3.
    Show Details
    Affected Modules: app

    框架是否考虑把DiffUtil加进来? 有个关于选中数据删除的bug

    就是移除选中的item之后再notifyDataSetChange更新,数据和rv是有变化了,但是checkCount是不会更新变化的,还是之前的。

    private val adapter get() = vb.rvUser.bindingAdapter
    private val checkCount get() = adapter.checkedCount
    private val checkList get() = adapter.getCheckedModels()

    fun delete(){
    adapter.mutable.removeAll(checkList)
    adapter.notifyDataSetChanged()
    //这里之后获取的checkedCount还是之前的数据

    我有试过把删除的position setCheck为false,但是我看了setCheck里面是checkPosition调用remove的,所以我循环iterator移除也依然会ConcurrentModificationException

    adapter.notifyItemRemove也是一样情况

    这种情况还是得自己管理一个选中的记录

    现在的实现方法是这样子的

    recyclerView.setup{
        registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
               recyclerView.smoothScrollToPosition(this@setup.modelCount)
               // 在收到onChanged后 直接滑动到最后一个
    

    测试了registerAdapterDataObserver中的其他回调 都收不到任何通知

    个人的一点思路(暂时没有多少时间完善代码,如采纳此需求可有空后提交pr)

    /** 数据模型集合 */
    var models: List<Any?>? = null
        set(value) {
            // TODO 在此处做一下数据的 Diff 判断,按需调用notify
             ......
    

    增加一个默认的AdapterDataObserver,通过开关管理在添加新数据后的动作

    这个库 不支持 Java 的啊?

    只用了implementation 'com.github.liangjingkanji:BRV:1.3.26'但是报错了

    Failed to resolve: com.scwang.smart:refresh-footer-classics:2.0.1
    Show in Project Structure dialog
    Affected Modules: app
    Failed to resolve: com.scwang.smart:refresh-layout-kernel:2.0.1
    Show in Project Structure dialog
    Affected Modules: app
    Failed to resolve: com.scwang.smart:refresh-header-material:2.0.1
    Show in Project Structure dialog
    Affected Modules: app
    

    但是sample是可以运行的,这3个也加到依赖去还是会报错

    作者,选中模式下,能否增加传入默认值。

    如题,在设置LinearLayoutManager 后,添加了分割线代码如下

    mDataBind.listRecyclerView.vertical().divider {
                orientation = DividerOrientation.VERTICAL
                startVisible = true
                endVisible = true
                setDivider(10,true)
                setMargin(8,8)
            }.adapter = testAdapter
    

    想通过setMargin方法设置左右两边的边距,但是没有效果,想问一下,目前万能分割线是否支持这种需求呢?

    感觉这个框架会异军突起 尤其在BRVAH常年不维护的情况下

    感觉这个框架会异军突起 尤其在BRVAH常年不维护的情况下

    刷新闪烁问题

    当我的recyclerView的Header里还有一个横向的recyclerView里时,两个recyclerview的数据还是通过两个接口获取时,这时候刷新两个列表都会闪一下。请问下作者是如何处理刷新闪烁问题的?谢谢。

    侧滑只能默认删除吗?可以类似与qq列表侧滑那样,出现自定义的图标或文字吗?

    仓库介绍中写着,无论是不是要databinding,但文档中并没有这么强调,所以想询问一下,下面的代码到底是必要的还是非必要的

    apply plugin: "kotlin-kapt" // kapt插件用于生成dataBinding
    android {
        /.../
        buildFeatures.dataBinding = true
    

    ` <com.drake.brv.PageRefreshLayout
    android:id="@+id/page"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_marginTop="6dp"
    android:layout_marginBottom="6dp"
    android:paddingLeft="4dp"
    android:paddingRight="4dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_toBottomOf="@id/ll_list_title"
    app:stateEnabled="true">

                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/rv_user"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent" />
                </com.drake.brv.PageRefreshLayout>`
    

    错误: cannot generate view binders com.sun.tools.javac.code.Symbol$CompletionFailure: 找不到android.support.v4.view.NestedScrollingParent的类文件

    试过1.3.33和1.3.35都是

    可否继承PagedListAdapter 用自有逻辑去分页,至于多布局等没想好怎么做