一个全新的RecyclerView-Adapter框架源码开源

15次阅读

共计 5649 个字符,预计需要花费 15 分钟才能阅读完成。

RecyclerViewAdapter

重新定义 RecyclerView Adapter 的封装,追求既简单又实用,结合 Kotlin 的高级特性,优化代码书写方式,真正做到高内聚低耦合

开源地址

Github RecyclerViewAdapter

框架设计核心思想

  • 摒弃 notifyDataSetChanged 无脑操作,利用 ObservableList 自动匹配数据,并实现局部刷新
  • 真正通用的 ViewHolder 抽象,从此只关注 Layout XML 布局
  • 真正通用的 Adapter,从此不再写 Adapter 子类
  • ItemViewType 自动匹配对象 Layout XML,不再关心它的细节
  • 科学的分包处理,真正做到框架的各取所需(一般列表只需引用 Adapter-core 核心库即可)
  • 扩展 Anko Layout 版本,体验 Anko Layout 的魅力,并能获取高于 XML 加载至少 3 倍以上的效率提升
  • 像堆积木一样,将页面的每个模块都做到了复用,跟 Fragment 可以说再见

设计图

规划

  • 核心库 Core 的完善
  • Anko 扩展
  • FlexboxLayout 扩展
  • SortedList 扩展
  • paging 3 扩展
  • DiffUtil 扩展
  • DataBinding 扩展
  • 等等.. 未来有好的想法继续扩展

环境需要

  • Kotlin
  • JAVA
  • AndroidX

抱歉目前按照最新的 AndroidX 适配的,如有其他需要请私聊我。

怎么用

ArrayListAdapter

step1

创建 xml 布局, 和之前一样的布局方式

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp"
    android:layout_margin="5dp">

    <LinearLayout
        android:background="?attr/selectableItemBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorPrimary"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/tv_subTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:textSize="18sp" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

step2

定义 ViewModel 及 Model,可以看到,逻辑简单明了,刷新自己的时候只需要更新 Model,并 reBindView 即可,刷新别人的话,需要通过 Adapter 去更新,复杂页面只需要再新建一个 ArrayItemViewModel 的子类即可,并创建一个新的 XML 布局,从这里的代码可以看出,同样一个 ViewModel 未来可以复用很多 XML 布局,完全做到了 ViewModel、View、Model 三个角色的任意复用。为业务多样化提供最底层的支持。

/**
 * Model
 */
data class ModelTest(var title: String, var subTitle: String)

/**
 * ViewModel
 */
class ArrayViewModelTest : ArrayItemViewModel<ModelTest>() {

    var index = 0

    override fun onBindView(adapter: ArrayListAdapter?) {
        viewHolder.itemView.apply {
            tv_title.text = model.title
            tv_subTitle.text = model.subTitle
            cardItem.setOnClickListener {model.title = "${index++}"
                reBindView()}
        }
    }

    override fun getLayoutRes() = R.layout.item_test}

复用逻辑如下图:

step3

Activity 中增删改,增删改都是对 ViewModel 层的操作,简单实用。

/**
 * Activity
 */
class ArrayListActivity : AppCompatActivity() {

    private val mArrayListAdapter by lazy {ArrayListAdapter()
    }

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_array_list)
        rv_list.bindListAdapter(mArrayListAdapter)


        // 新增一个
        new_add.setText("新增").setOnClickListener {mArrayListAdapter.add(ArrayViewModelTest().apply {model = ModelTest("标题", "副标题")
            })
        }


        // 删除第一个
        delete.setText("删除").setOnClickListener {if (mArrayListAdapter.size > 0)
                mArrayListAdapter.removeAt(0)
            else
                toast("请添加新用例后再试")
        }


        // 随机更新
        var updateSize = 0
        update.setText("更新").setOnClickListener {
            updateSize++
            if (mArrayListAdapter.size > 0) {val randomInt = Random.nextInt(0, mArrayListAdapter.size)
                mArrayListAdapter.set(randomInt, ArrayViewModelTest().apply {model = ModelTest("标题 $updateSize", "副标题 $updateSize")
                })
            } else {toast("请添加新用例后再试")
            }

        }

    }
}

AnkoListAdapter

step1

定义 AnkoLayout

/**
 * AnkoItemView
 */
class AnkoItemView() : AnkoComponent<ViewGroup> {

    var tvTitle: TextView? = null
    var tvSubTitle: TextView? = null

    @SuppressLint("ResourceType")
    override fun createView(ui: AnkoContext<ViewGroup>) = with(ui) {

        cardView {

            layoutParams = FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
            ).apply {margin = dip(5)
            }

            verticalLayout {val typedValue = TypedValue()
                context.theme
                    .resolveAttribute(android.R.attr.selectableItemBackground, typedValue, true)
                val attribute = intArrayOf(android.R.attr.selectableItemBackground)
                val typedArray =
                    context.theme.obtainStyledAttributes(typedValue.resourceId, attribute)

                background = typedArray.getDrawable(0)

                layoutParams = FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.WRAP_CONTENT
                ).apply {padding = dip(10)
                }

                tvTitle = textView {textSize = px2dip(60)
                    textColorResource = R.color.colorPrimary
                }.lparams(matchParent, wrapContent)

                tvSubTitle = textView {textSize = px2dip(45)
                    textColorResource = R.color.colorAccent
                }.lparams(matchParent, wrapContent)

            }

        }

    }
}

step2

定义 ViewModel,Model

/**
 * Model
 */
data class ModelTest(var title: String, var subTitle: String)

/**
 * ViewModel
 */
class AnkoViewModelTest : AnkoItemViewModel<ModelTest, AnkoItemView>() {

    var index = 0

    override fun onBindView(adapter: AnkoListAdapter) {

        ankoView.tvTitle?.text = model.title
        ankoView.tvSubTitle?.text = model.subTitle

        viewHolder.itemView.setOnClickListener {model.title = "${index++}"
            reBindView()}

    }

    override fun onCreateView(): AnkoItemView {return AnkoItemView()
    }
}

step3

Activity 中增删改

/**
 * Activity
 */
class AnkoLayoutActivity : AppCompatActivity() {

    private val mAnkoListAdapter by lazy {AnkoListAdapter()
    }

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)

        AnkoLayoutComponent(mAnkoListAdapter).setContentView(this).apply {

            // 新增一个
            new_add.setText("新增").setOnClickListener {mAnkoListAdapter.add(AnkoViewModelTest().apply {model = ModelTest("标题", "副标题")
                })
            }


            // 删除第一个
            delete.setText("删除").setOnClickListener {if (mAnkoListAdapter.size > 0)
                    mAnkoListAdapter.removeAt(0)
                else
                    toast("请添加新用例后再试")
            }


            // 随机更新
            var updateSize = 0
            update.setText("更新").setOnClickListener {
                updateSize++
                if (mAnkoListAdapter.size > 0) {val randomInt = Random.nextInt(0, mAnkoListAdapter.size)
                    mAnkoListAdapter.set(randomInt, mAnkoListAdapter.getItem(randomInt).apply {
                        model.also {
                            it as ModelTest
                            it.title = "$updateSize"
                        }
                    })
                } else {toast("请添加新用例后再试")
                }
            }

        }
    }

}

/**
 * View
 *
 */
class AnkoLayoutComponent(private val ankoListAdapter: AnkoListAdapter) : AnkoComponent<AnkoLayoutActivity> {override fun createView(ui: AnkoContext<AnkoLayoutActivity>) = with(ui) {

        verticalLayout {

            recyclerView {bindListAdapter(ankoListAdapter)
            }.lparams(matchParent) {weight = 1F}
            // Anko 兼容 xml 布局的加载
            include<View>(R.layout.include_button_bottom)

        }

    }

}

上一篇博客

一个资深的 Android 是不是应该学会自己做一个超级的 RecyclerView.Adapter

开发者

  • i 校长

    • Jetpack.net.cn
    • 简书
    • 掘金

正文完
 0