后现代化RecyclerView Adapter稳定版本终于来了

简介: 后现代化RecyclerView Adapter稳定版本终于来了

背景


相信大家都已经在使用kotlin了,可我们使用最频繁的Adapter缺很少有人用kotlin做扩展,即使有,但给我的感觉还是不够,第一不够简洁,第二功能耦合在一起,第三不够完善,于是我决定自己做一个,经过这段时间的研究,前面也写了三篇博客了,都是我这段时间的劳动成果,可之前的设计还是会有一些不好的地方,也是经过几次的验证后,目前有了稳定版,对于这个版本我还是比较满意的,下面有请我厚脸皮给你们讲一讲

源码地址


github.com/ibaozi-cn/R…

Gradle依赖


allprojects {
    repositories {
        // 首先项目根目录的build.gradle文件中加入这一行 
        maven { url 'https://jitpack.io' }
    }
}
def adapterVersion = 'v1.2.0'
//核心库
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-core:$adapterVersion"
//下面都是可选项
//anko layout 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-anko:$adapterVersion"
//diffutil 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-diff:$adapterVersion"
//data binding扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-binding:$adapterVersion"
// paging3 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-paging:$adapterVersion"
// sortedlist 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-sorted:$adapterVersion"
// flexbox 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-flex:$adapterVersion"
// UI 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-ui:$adapterVersion"
// Selectable 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-selectable:$adapterVersion"
// Expandable 扩展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-expandable:$adapterVersion"

当前版本库大小


名字 release aar size 其他
Core 28kb 核心库目前包含ListAdapter的实现,最基础且最实用的扩展
Anko 13kb 适用本项目所有Adapter扩展
DataBinding 20kb 适配DataBinding布局,适用本项目所有Adapter扩展
Sorted 10kb SortedListAdapter扩展实现
Paging 13kb PagingListAdapter扩展适配
Diff 6kb 适配DiffUtil,目前适用ListAdapter
FlexBox 9kb 适配FlexBox布局
Selectable 8kb 动态扩展单选、多选、最大可选项功能
Expandable 8kb 动态扩展可展开功能,支持仅单展开或多展开配置
UI 17kb 扩展空布局

对Adapter扩展类图


image.png

上面的内容我大致描述一下

  • IAdapter 最底层的抽象接口
  • ViewHolderCacheAdapter 统一处理ViewHolder的缓存和ViewModel的回调
  • ListAdapter 扩展ViewHolderCacheAdapter,实现对ArrayList数据结构的处理
  • SortedListAdapter 扩展ViewHolderCacheAdapter,实现对SortedList数据结构的处理
  • PagingListAdapter 扩展ViewHolderCacheAdapter,实现对AsyncPagingDataDiffer的处理
  • IAdapter Expandable 动态扩展 这里用的Kotlin的扩展函数实现,类似组合继承,解耦方便
  • IAdapter Selectable 动态扩展 同上
  • ViewModel 这个是对Adapter每一个Item的高度抽象,负责配置Model数据,负责获取ViewHolder的实例,缓存itemViewType等
  • DefaultViewModel 负责创建DefaultViewHolder,并提供ViewHolder初始化InitView的回调,这个很关键,这是我们可以直接在ViewModelDSl中findView的关键
  • LayoutViewModel 扩展自DefaultViewModel,实现LayoutInflater加载View
  • AnkoViewModel 扩展自DefaultViewModel,实现AnkoComponent加载View
  • BindingViewModel 扩展自DefaultViewModel,实现DataBindingUtil加载View和ViewDataBinding绑定数据
  • WrapAdapter 专门负责装饰老的Adapter适配器,一种很好的设计模式,比起继承实现要好太多了,EmptyAdapter就是一个很好的实现,只需要将以前的Adapter包裹一层EmptyAdapter,就可以轻松实现空布局,这里我想啰嗦一句,如果是头尾布局,不建议这么做,我们RecyclerView升级1.2.0以后就会有一个ConcatAdapter,这个适配器才是我们实现头尾布局的神器,期待ing

这么好的框架如何使用呢?


源码中提供全面的例子,Demo目录如图哦

image.png

一些效果图

image.png

image.png

image.png

image.png

下载源码,跑一下Demo就行了哦,下面教你如何快速的上手

快速上手


LayoutViewModel 例子

//xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/cardItem"
    android:layout_margin="5dp">
    <LinearLayout
        android:background="?attr/selectableItemBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="25dp"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/tv_title"
            android:text="@string/app_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorPrimary"
            android:textSize="22sp" />
        <TextView
            android:id="@+id/tv_subTitle"
            android:text="@string/test"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:layout_marginStart="10dp"
            android:textSize="18sp" />
    </LinearLayout>
</androidx.cardview.widget.CardView>
// 传入布局 和 Model 数据
layoutViewModelDsl(R.layout.item_test, ModelTest("title", "subTitle")) {
    // 初始化 View,这里只会调用一次哦,放心初始化,放心的setOnClickListener
        val titleText = getView<TextView>(R.id.tv_title)
        val subTitleText = getView<TextView>(R.id.tv_subTitle)
        itemView.setOnClickListener {
            val vm = getViewModel<LayoutViewModel<ModelTest>>()
            //修改Model数据
            vm?.model?.title = "测试更新${Random.nextInt(10000)}"
            //用Adapter更新数据
            getAdapter<ListAdapter>()?.set(adapterPosition, vm)
        }
        //数据触发更新的时候,绑定新的Model数据
        onBindViewHolder {
            val model = getModel<ModelTest>()
            titleText.text = model?.title
            subTitleText.text = model?.subTitle
        }
    }

AnkoViewModel 例子

// view
class AnkoItemView : AnkoComponent<ViewGroup> {
    var tvTitle: TextView? = null
    var tvSubTitle: TextView? = null
    var view: View? = 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)
            }
        }
    }
}
// 传入Model和AnkoView对象
ankoViewModelDsl(ModelTest("title", "ankoViewModelDsl"), { AnkoItemView() }) {
    //数据更新
        onBindViewHolder { _ ->
            val model = getModel<ModelTest>()
            val ankoView = getAnkoView<AnkoItemView>()
            ankoView?.tvTitle?.text = model?.title
            ankoView?.tvSubTitle?.text = model?.subTitle
        }
        //点击事件处理
        itemView.setOnClickListener {
            val viewModel = getViewModel<AnkoViewModel<ModelTest, AnkoItemView>>()
            viewModel?.model?.title = "点击更新${Random.nextInt(10000)}"
            getAdapter<ListAdapter>()?.set(adapterPosition, viewModel)
        }
    }

与LayoutViewModel的不同就在于无需在DSL中初始化View,因为已经在AnkoView中做了缓存,它唯一的优势就是比LayoutViewModel更快的加载速度,但Anko Layout已经不维护了,你是不是不敢用了呢?在我看来,问题不大,因为我可以自定义AnkoView,自己来做扩展,性能的提升远大于代码的数量,你说呢?

BindingViewModel 例子

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <data>
        <variable
            name="model"
            type="com.julive.adapter_demo.sorted.ModelTest" />
    </data>
    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/cardItem"
        android:layout_margin="5dp">
        <LinearLayout
            android:background="?attr/selectableItemBackground"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="25dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_title"
                android:text="@{model.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:text="@{model.subTitle}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@color/colorAccent"
                android:layout_marginStart="10dp"
                android:textSize="18sp" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</layout>
//传入layout、BR、Model
bindingViewModelDsl(R.layout.item_binding_layout, BR.model, ModelTest("title", "bindingViewModelDsl")) {
    //设置点击事件
        itemView.setOnClickListener {
            val viewModel = getViewModel<BindingViewModel<ModelTest>>()
            viewModel?.model?.title = "${java.util.Random().nextInt(100)}"
            getAdapter<ListAdapter>()?.set(adapterPosition, viewModel)
        }
    }

没有findView,没有onBindViewHolder,代码缩减了很多,如果你追求的就是高效率,请使用它,准没错,三种加载ItemView的方式就完了

如何加载到Adapter中呢


listAdapter {
       addAll(createViewModelList(3))
       addAll(createAnkoViewModelList(3))
       addAll(createBindingViewModelList(3))
       // 绑定 RecyclerView
       into(rv_list_dsl)
}
fun createViewModelList(max: Int = 10) = (0..max).map { _ ->
    layoutViewModelDsl(R.layout.item_test, ModelTest("title", "subTitle")) {
        val titleText = getView<TextView>(R.id.tv_title)
        val subTitleText = getView<TextView>(R.id.tv_subTitle)
        itemView.setOnClickListener {
            val vm = getViewModel<LayoutViewModel<ModelTest>>()
            //修改Model数据
            vm?.model?.title = "测试更新${Random.nextInt(10000)}"
            //用Adapter更新数据
            getAdapter<ListAdapter>()?.set(adapterPosition, vm)
        }
        onBindViewHolder {
            val model = getModel<ModelTest>()
            titleText.text = model?.title
            subTitleText.text = model?.subTitle
        }
    }
}
省略Anko、Binding...

如何实现一个Selectable呢


class SelectableActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.title = "ListAdapter"
        setContentView(R.layout.activity_selectable)
        //同样是ListAdapter
        val adapter = listAdapter {
          //添加一堆ViewModel数据
            (0..10).forEach { _ ->
                add(
                    layoutViewModelDsl(
                        R.layout.item_test,
                        ModelTest("title", "subTitle")
                    ) {
                        //初始化View
                        val title = getView<TextView>(R.id.tv_title)
                        val subTitle = getView<TextView>(R.id.tv_subTitle)
                        //设置监听
                        itemView.setOnClickListener {
                            //改变选择状态
                            toggleSelection(adapterPosition) {
                                if (it) {
                                    longToast("可选项已达到最大值")
                                }
                            }
                            Log.d("isMultiSelectable", "isMultiSelectable$isMultiSelect")
                        }
                        onBindViewHolder {
                            val model = getModel<ModelTest>()
                            title.text = model?.title
                            subTitle.text = model?.subTitle
                            // 获取选择状态,来适配不同UI
                            val isSelect = isSelected(adapterPosition)
                            if (isSelect) {
                                itemView.setBackgroundResource(R.color.cardview_dark_background)
                                title.textColorResource = R.color.cardview_light_background
                            } else {
                          itemView.setBackgroundResource(R.color.cardview_light_background)
                                title.textColorResource = R.color.cardview_dark_background
                            }
                        }
                    })
            }
            into(rv_list_selectable)
        }
        btn_left.setText("切换单选").setOnClickListener {
            // 多选和单选之间切换
            if (!adapter.isMultiSelect) {
                btn_left.setText("切换单选")
            } else {
                btn_left.setText("切换多选")
            }
            adapter.setMultiSelectable(!adapter.isMultiSelect)
        }
        btn_middle.isVisible = false
        btn_right.setText("设置最大可选").setOnClickListener {
            //配置最大的可选择项
            val random = Random().nextInt(6)
            btn_right.setText("设置最大可选$random")
            adapter.setSelectableMaxSize(random)
        }
    }
}

有没有前所未有简单呢?这就是Kotlin动态扩展函数带来的便利,下面请看下实现的源码:

//根据列表实例缓存已选择项,只缓存选中的,未选中的会被清理掉节省内存,用弱引用来提高内存回收率
private val selectedItemsCache = SparseArray<WeakReference<SparseBooleanArray?>?>()
private val selectConfigCache = SparseArray<WeakReference<SparseArray<Any>?>?>()
//可选项默认配置
private val defaultSelectedConfig by lazy {
    SparseArray<Any>().apply {
        append(0, true) // is Multi Selectable
        append(1, Int.MAX_VALUE) // Selectable Max Size Default Int.Max
    }
}
// 获取已选择列表
private fun getSelectedItems(key: Int): SparseBooleanArray {
    val wr = selectedItemsCache[key]
    val sba by lazy {
        SparseBooleanArray()
    }
    return if (wr == null) {
        selectedItemsCache.append(key, WeakReference(sba))
        sba
    } else {
        val expandedItems = wr.get()
        if (expandedItems == null) {
            selectedItemsCache.append(key, WeakReference(sba))
        }
        expandedItems ?: sba
    }
}
// 获取选择项配置信息
private fun getSelectConfig(key: Int): SparseArray<Any> {
    val wr = selectConfigCache[key]
    return if (wr == null) {
        selectConfigCache.append(key, WeakReference(defaultSelectedConfig))
        defaultSelectedConfig
    } else {
        val expandConfig = wr.get()
        if (expandConfig == null) {
            selectConfigCache.append(key, WeakReference(defaultSelectedConfig))
        }
        expandConfig ?: defaultSelectedConfig
    }
}
// 动态扩展IAdapter 判断当前是否多选
var IAdapter<*>.isMultiSelect
    get() = getSelectConfig(hashCode())[0] as Boolean
    private set(value) {}
// 动态扩展IAdapter 获取最大可选择数
var IAdapter<*>.selectedMaxSize: Int
    get() = getSelectConfig(hashCode())[1] as Int
    private set(value) {}
// 动态扩展IAdapter 获取已选择项的大小
var IAdapter<*>.selectedCount: Int
    get() = getSelectedItems(hashCode()).size()
    private set(value) {}
// 动态扩展IAdapter 配置多选和单选的状态
fun IAdapter<*>.setMultiSelectable(enable: Boolean) {
    getSelectConfig(hashCode()).setValueAt(0, enable)
    if (!enable && selectedCount > 1) {
        clearSelection()
    }
}
// 动态扩展IAdapter 配置最大可选择数
fun IAdapter<*>.setSelectableMaxSize(size: Int) {
    getSelectConfig(hashCode()).setValueAt(1, size)
}
// 动态扩展IAdapter 获取已选择列表
fun IAdapter<*>.getSelectedItems(): List<Int> {
    val si = getSelectedItems(hashCode())
    val itemSize = si.size()
    val items: MutableList<Int> = ArrayList(itemSize)
    for (i in 0 until itemSize) {
        items.add(si.keyAt(i))
    }
    return items
}
// 动态扩展IAdapter 判断当前是否已选择
fun IAdapter<*>.isSelected(position: Int) = getSelectedItems().contains(position)
// 动态扩展IAdapter 清空已选择项
fun IAdapter<*>.clearSelection() {
    val selection = getSelectedItems()
    getSelectedItems(hashCode()).clear()
    for (i in selection) {
        notifyItemChanged(i)
    }
}
//动态扩展IAdapter 改变选择状态
fun IAdapter<*>.toggleSelection(position: Int, isMaxSelect: ((Boolean) -> Unit)? = null) {
    val si = getSelectedItems(hashCode())
    val isSelect = si.get(position, false)
    if (selectedCount >= selectedMaxSize && !isSelect) {
        isMaxSelect?.invoke(true)
        return
    }
    isMaxSelect?.invoke(false)
    if (!isMultiSelect) {
        clearSelection()
    }
    if (isSelect) {
        si.delete(position)
    } else {
        si.put(position, true)
    }
    notifyItemChanged(position)
}

没有继承,没有组合,就是动态扩展,这样的解耦方式,是不是比以前更加的好用呢?我认为还可以,不知道你怎么想,欢迎留言。Expandable实现原理同上,就不再描述了哦

WrapAdapter的实现


为什么要有WrapAdapter?还是以前的例子,来看下那个截图

e0f359e04f3f4c7aa42209c87f7b7383_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

看到没,这里面就有一个EMPTY_VIEW,不光这些还有头尾布局,这样的逻辑你敢用吗?如果有了WrapAdapter是什么样子呢?

override fun getItemViewType(position: Int): Int {
        return if (displayEmptyView(emptyState)) {
            viewModel.itemViewType
        } else {
            super.getItemViewType(position)
        }
    }

就这样就行了,简单明了,这么简洁的代码你不点个赞吗?哈哈,其实这就是装饰者模式的魅力,其实它的核心就是将真实适配器的调用权交给了WrapAdapter,然后在合适的时机再调用真实的Adapter来展示数据。其实WrapAdapter的实现很简单,来看下一段代码

// 继承自RecyclerView.Adapter 可以传入一个新的Adapter
open class WrapAdapter<VH : RecyclerView.ViewHolder>(private var mWrappedAdapter: RecyclerView.Adapter<VH>) :
    RecyclerView.Adapter<VH>()
// 注册observer,实现notifyDataChange一系列相关回调
mWrappedAdapter.registerAdapterDataObserver(wrapAdapterDataObserver)
// 一些关键函数的调用实现,这里没写全哦,详细还请跳入源码查看
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        return mWrappedAdapter.onCreateViewHolder(parent, viewType)
    }
    override fun getItemId(position: Int): Long {
        return mWrappedAdapter.getItemId(position)
    }
    override fun getItemViewType(position: Int): Int {
        return mWrappedAdapter.getItemViewType(position)
    }
    override fun onBindViewHolder(holder: VH, position: Int, payloads: List<Any>) {
        mWrappedAdapter.onBindViewHolder(holder, position, payloads)
    }

WrapAdapter这么好,空布局如何用的呢?


class EmptyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_empty)
        val emptyAdapter = EmptyAdapter(
            listAdapter {
                addAll(createViewModelList())
            }
        ).apply {
            into(rv_list_empty)
        }
        btn_left.setText("空布局").setOnClickListener {
            emptyAdapter.emptyState = EmptyState.NotLoading
        }
        btn_middle.setText("加载中").setOnClickListener {
            emptyAdapter.emptyState = EmptyState.Loading
            Handler().postDelayed({
                emptyAdapter.emptyState = EmptyState.Loaded
            },2000)
        }
        btn_right.setText("加载失败").setOnClickListener {
            emptyAdapter.emptyState = EmptyState.Error
        }
    }
}

也是超级简单是吧,很容易就理解了对吗?

如何使用SortedListAdapter呢?


我们先来看一段代码

/**
 * sortedId 排序用
 * title 作为uniqueId ,RecyclerView ItemView 更新的时候,唯一值,注意列表是可以出现一样的uniqueId的,
 * 如果想更新请调用Adapter updateItem 这样才能保证列表中uniqueId唯一
 */
data class SortedModelTest(
    val sortedId: Int, var title: String, var subTitle: String,
    override var uniqueId: String = title
) : SortedModel {
    override fun <T : SortedModel> compare(model: T): Int {
        if (sortedId > (model as? SortedModelTest)?.sortedId ?: 0) return 1
        if (sortedId < (model as? SortedModelTest)?.sortedId ?: 0) return -1
        return 0
    }
}
class SortedItemViewModelTest : LayoutViewModel<SortedModelTest>(R.layout.item_test) {
    init {
        onCreateViewHolder {
            itemView.setOnClickListener {
                val vm =
                    getAdapter<SortedListAdapter>()?.getItem(adapterPosition) as SortedItemViewModelTest
                vm.model?.subTitle = "刷新自己${Random.nextInt(100)}"
                getAdapter<SortedListAdapter>()?.set(adapterPosition, vm)
            }
        }
    }
    override fun bindVH(viewHolder: DefaultViewHolder, payloads: List<Any>) {
        viewHolder.getView<TextView>(R.id.tv_title).text = model?.title
        viewHolder.getView<TextView>(R.id.tv_subTitle).text = model?.subTitle
    }
}

我们抽象的ViewModel是在任何Adapter中都可以做到通用的,这点你可以放心哦,SorteList我们都知道它是需要对数据进行比较的,所以我们提供了SortedModel接口,你只需要实现SortedModel接口就可以将其放入ViewModel中,然后再放入Adapter中就行了,SortedModel实现SameModel,这里是接口继承,在kotlin里面接口是可以有实现的,

interface SameModel {
    var uniqueId: String
    //是否是同一个Model
    fun <T : SameModel> isSameModelAs(model: T): Boolean {
        return this.uniqueId == model.uniqueId
    }
    //同一个Model的话,数据是否有变化
    fun <T : SameModel> isContentTheSameAs(model: T): Boolean {
        return this == model
    }
    //局部刷新时使用
    fun <T : SameModel> getChangePayload(newItem: T): Any? = null
}
interface SortedModel : SameModel {
    /**
     * 排序使用
     */
    fun <T : SortedModel> compare(model: T): Int
}

由于是继承接口实现,所以侵入性不高,对于一般的业务都可以适用,你可以放心大胆的使用哦。在Activity中使用方法如下:

class SortedActivity : AppCompatActivity() {
    private val mSortedListAdapter by lazy {
        SortedListAdapter()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.title = "SortedListAdapter"
        setContentView(R.layout.activity_array_list)
        mSortedListAdapter.into(rv_list)
        // 初始化数据
        (0..10).map {
            mSortedListAdapter.add(SortedItemViewModelTest().apply {
                model = SortedModelTest(it, "标题$it", "副标题$it")
            })
        }
        var index = 100
        btn_left.setText("新增").setOnClickListener {
            // 要想根据uniqueId更新数据,需要调用updateItem方法
            mSortedListAdapter.add(SortedItemViewModelTest().apply {
                model = SortedModelTest(index++, "标题$index", "新增$index")
            })
        }
        btn_middle.setText("删除").setOnClickListener {
            if (mSortedListAdapter.size > 0) {
                val randomInt = Random.nextInt(0, mSortedListAdapter.size)
                mSortedListAdapter.removeAt(randomInt)
            }
        }
        btn_right.setText("替换").setOnClickListener {
            // 根据uniqueId替换 如果sortId不一样就会触发排序
            if (mSortedListAdapter.size > 0) {
                val randomInt = Random.nextInt(0, mSortedListAdapter.size)
                mSortedListAdapter.set(randomInt, mSortedListAdapter.getItem(randomInt).also {
                    it as SortedItemViewModelTest
                    it.model?.subTitle = "替换副标题"
                })
            }
        }
    }
}

未来更多的规划


  • 上啦加载更多,滚动底部或头部回调、获取可见项
  • 拖动处理、滑动删除
  • 基础动画
  • Item边距处理
  • 树的展开扩展,目前展开只是支持了一层,未来实现多层展开
  • StickyHeader扩展,列标题实现
  • 等等吧

这么全面的Adapter你见过几个?还不动动小手关注一哈,嘿嘿,谢谢🙏

总结


我这期针对稳定版本,写的不是很多,主要就是为了让你们知道如何使用,以及一些源码的展示,其实我们在做开发的同时,真的会遇到各种各样的列表,当然它不能覆盖业务中的各个场景,但我希望能在某些实现的角度上能让你收益,用更加合理的实现方式来解决业务中各种各样的复杂场景。

感谢


github.com/mikepenz/Fa…

github.com/DevAhamed/M…

github.com/davideas/Fl…

github.com/liangjingka…

github.com/h6ah4i/andr…

github.com/evant/bindi…

特别感谢这些优秀设计者的项目,是他们的经验积累,让我有了更多的想法和实现。

目录
相关文章
|
23天前
|
JavaScript 前端开发 开发工具
Flutter&鸿蒙next 中如何实现 WebView【跳、显、适、反】等一些基础问题
在 Flutter 项目中集成 WebView 可以展示网页或进行在线操作。本文介绍了如何添加依赖、配置平台权限、创建 WebView 页面、适配不同机型、处理页面间参数传递等详细步骤,帮助开发者高效实现 WebView 功能,提升用户体验。
112 4
|
6月前
|
存储 缓存 Android开发
构建高效的Android应用:采用RecyclerView优化列表显示
【4月更文挑战第2天】 在移动开发领域,列表显示是最常见的用户界面组件之一。对于Android平台而言,RecyclerView因其高效、灵活的特点而备受开发者青睐。本文将深入探讨如何利用RecyclerView在Android应用中实现流畅的列表滚动,以及通过各种优化策略来提升性能和用户体验。我们将从基本概念出发,逐步展开如何自定义适配器、视图持有者,以及利用布局管理器来实现复杂的列表布局。此外,还将讨论如何通过异步加载、缓存机制和动态数据更新来进一步优化性能。
92 1
|
数据库 Android开发 数据库管理
Android使用Room操作SQLite数据库让其变得无比高效和简洁(进一步完善用RecyclerView显示数据库中的数据)
Android使用Room操作SQLite数据库让其变得无比高效和简洁(进一步完善用RecyclerView显示数据库中的数据)
79 0
|
XML SQL 前端开发
Adapter基础讲解
这一节我们要讲的UI控件都是跟Adapter(适配器)打交道的,了解并学会使用Adapter很重要, Adapter是用来帮助填充数据的中间桥梁,简单来说就是:将各种数据以合适的形式显示到view上,提供 给用户看!
454 0
|
缓存 JSON ARouter
Android开源系列-组件化框架Arouter-(一)使用方式详解
最近组里需要进行**组件化框架**的改造,用到了`Arouter`这个开源框架,为了更好的对项目进行改造,笔者花了一些时间去了解了下`Arouter
|
存储 ARouter Java
Android开源系列-组件化框架Arouter-(三)APT技术详解
最近组里需要进行**组件化框架**的改造,用到了**ARouter**这个开源框架,为了更好的对项目进行改造,笔者花了一些时间去了解了下ARouter
|
XML 缓存 算法
一个全新的RecyclerView Adapter框架源码开源
一个全新的RecyclerView Adapter框架源码开源
288 0
一个全新的RecyclerView Adapter框架源码开源
|
XML 缓存 算法
重学RecyclerView Adapter封装的深度思考和实现
重学RecyclerView Adapter封装的深度思考和实现
261 0
重学RecyclerView Adapter封装的深度思考和实现
|
Android开发
【安卓开发】借助setOnItemClickListener动态更新Listview
【安卓开发】借助setOnItemClickListener动态更新Listview
144 0
【安卓开发】借助setOnItemClickListener动态更新Listview
|
缓存 Android开发
聊聊RecyclerView新出的ConcatAdapter如何使用
聊聊RecyclerView新出的ConcatAdapter如何使用
聊聊RecyclerView新出的ConcatAdapter如何使用