Skip to content

数据刷新,更新和加载更多

angcyo edited this page Jul 6, 2020 · 1 revision

简述

DslAdapter唯一能接受的数据类型就是DslAdapterItem.

DslAdapterItem是一个超级对象(以下简称Item), 通常开发者都需要继承此类, 实现界面组件.

Item是组成界面的最小单位, 通过多个Item最终组成简单/复杂的界面.

使用

比如Demo中的DslDemoItem

class DslDemoItem : DslAdapterItem() {

    var itemText: CharSequence? = null

    init {
        itemLayoutId = R.layout.item_demo_list
    }

    override fun onItemBind(
        itemHolder: DslViewHolder,
        itemPosition: Int,
        adapterItem: DslAdapterItem,
        payloads: List<Any>
    ) {
        super.onItemBind(itemHolder, itemPosition, adapterItem, payloads)
        itemHolder.tv(R.id.text_view)?.text = itemText
    }
}

几个关键点

  1. 继承自DslAdapterItem //必须
  2. 设置itemLayoutId //必须
  3. 重写onItemBind //可选. (因为还有很多同功能的API)

对于初学者, 会使用上面3步即可.

声明数据

在上述的DslDemoItem中, itemText就是DslDemoItem的数据.

当然, 您也可以声明多个任意命名 任意类型的数据, 库对此毫无依赖.

如此声明之后, 如何在界面展示呢?

界面展示

Item需要在界面能看到, 那么Item就需要关联到DslAdapter, 然后DslAdapter关联RecyclerView, 这样就能在界面上展示了.

DslAdapter提供了多种api, 方便关联Item, 但最终核心都是将Item放到DslAdapter内部的footerItems headerItems或者dataItems成员变量中.

最后不要忘了, 调用dslAdapter.updateItemDepend刷新界面哦.

这些个列表数据集合, 通过一个叫做DslDataFilter过滤器, 摘取部分或者全部Item丢到RecyclerView中展示.

Q: 为什么会出现部分数据丢到RecyclerView中展示呢?

A: 这是因为DslAdapter是支持Item隐藏/分组折叠的.被隐藏的Item虽然依旧存在DslAdapter中, 但是界面上却不存在.

关于 footerItems headerItems dataItems

其实理论上只需要一个dataItems即可, 当初设计的时候, 为了大家方便管理, 才多加了几个数据集合.

footerItems headerItems dataItems数据集合的数据, 在更新到界面RecycleView之前, 都会被合并到一个叫做adapterItems数据集合中, 不推荐直接操作adapterItems, 当然如果您对库了如指掌, 也是可以直接使用此变量的.

无特殊说明的情况下, 库中API默认只操作dataItems

要将Item关联到DslAdapter提供了以下多种方法:

单一操作

dslAdapter.addLastItem(item)
dslAdapter.addLastItem(list<item>)

dslAdapter.insertItem(0, item)
dslAdapter.resetItem(list<item>) //先clear, 后addAll

//移除操作
dslAdapter.removeItem(item)
dslAdapter.removeItem(list<item>)

//以上方法都会默认触发`updateItemDepend`
//如果需要, 也可以手动触发`updateItemDepend`

kotlin扩展操作

库中重写了一些操作符, 所以你可以使用以下方式:

dslAdapter + DslAdapterItem().apply{}

dslAdapter - DslAdapterItem()

// 这是本人推荐的使用方法
dslAdapter.apply{
    DslDemoItem()(){
        ...
    }
}

超级操作

库中提供了3个超级API,用于直接操作对应的数据源列表:

changeDataItems{ dataItems->
  //直接操作dataItems
}

changeHeaderItems{ headerItems->
  //同上
}
 
changeFooterItems{ footerItems->
  //同上  
}

//以上方法都可以直接操作对应的数据源, 操作后并会更新界面
//以上方法都会默认触发`updateItemDepend`

更新Item

如何更新已经Item呢?

  1. 通过Tag查找对应Item,并更新.

设置DslAdapterItem的成员变量itemTag, 然后通过dslAdapter.findItemByTag(xxx)扩展方法, 找到对应的Item, 最后调用item.updateAdapterItem刷新界面.

//1.
dslAdapter.apply{
    DslDemoItem()(){
        itemTag = "xxx" //设置tag
    }
}

//2.
dslAdapter.findItemByTag("xxx")?.apply {
    if (this is DslDemoItem) {
        itemText = "数据刷新:${System.currentTimeMillis()}"
        updateAdapterItem()
    }
}
  1. 如何在列表中更新Item呢?

不管在什么地方更新Item, 核心都是1中的思路, 拿到目标Item更新对应的数据成员变量, 刷新界面. 完事收工.

只不过, 作为库的开发者. 要时刻为了使用者考虑, 减轻使用者的使用成本.

所以, 库提供了很多扩展方法, 方便操作. 但请注意上述的核心, 因为万变不离其宗. 我写的API, 也是围绕核心步骤编写的.

为了方便在列表中更新Item, 我写了一个Config叫UpdateDataConfig

UpdateDataConfig

这个类的可配置项如下:

updateDataList

var updateDataList: List<Any>? = null

本次更新的数据集合列表, 通常会是网络请求返回的list<bean>数据集合, 支持任意类型, 直接赋值塞过来即可.

updatePage,pageSize

var updatePage: Int = Page.FIRST_PAGE_IN
var pageSize: Int = Page.PAGE_SIZE

这2个参数, 是用来控制分页加载的. updatePage用来控制当前数据是第几页的,pageSize用来控制当前页请求的数据量(数据量过多时, DslAdapter底部会出现加载更多Item. 数据量不够时, DslAdapter底部会出现我是有底线Item).

updateOrCreateItem

/**
* 更新已有的item, 创建不存在的item, 移除不需要的item
* [oldItem] 如果有值, 则希望更新[oldItem]
* @return 返回null, 则会删除对应的[oldItem]返回与[oldItem]不一样的item, 则会替换原来的[oldItem]
* */
var updateOrCreateItem: (oldItem: DslAdapterItem?, data: Any?, index: Int) -> DslAdapterItem? =
    { oldItem, data, index ->
        oldItem
    }

核心处理方法, 这个方法用来处理, 更新当前界面相同的item, 还是删除当前界面的item, 还是在当前界面添加item. 注释写的很清楚了.

adapterUpdateResult

var adapterUpdateResult: (dslAdapter: DslAdapter) -> Unit = { dslAdapter ->
    with(dslAdapter) {
        if (dataItems.isEmpty() && headerItems.isEmpty() && footerItems.isEmpty()) {
            //空数据
            setAdapterStatus(DslAdapterStatusItem.ADAPTER_STATUS_EMPTY)
        } else {
            setAdapterStatus(DslAdapterStatusItem.ADAPTER_STATUS_NONE)
        }
    }
    adapterCheckLoadMore(dslAdapter)
}

用于自动控制切换情感图, 如果不需要, 可以置空.

adapterCheckLoadMore

var adapterCheckLoadMore: (dslAdapter: DslAdapter) -> Unit = { dslAdapter 
    dslAdapter.updateLoadMore(
        updatePage,
        if (updateDataList.isListEmpty()) 0 else (updateDataList?.size ?: 
        pageSize,
        alwaysEnableLoadMore
    )
}

用于自动控制加载更多 无更多, 如果不需要, 可以置空.

可以通过以下方法直接使用UpdateDataConfig:

dslAdapter.updateData{
    //此时的上下文对象就是`UpdateDataConfig`
    
    //此dsl区域内, 可以直接操作`UpdateDataConfig`对象的成员变量
    
    //操作完之后, 界面会触发刷新
}

为了更简洁的使用方法, 我还提供了以下更贴心的API:

统一加载数据方式loadDataEnd

API声明:

/**
 * 单一数据类型加载完成后, 调用此方法.
 * 自动处理, 情感图切换, 加载更多切换.
 * 
 * [itemClass] 渲染界面的`DslAdapterItem`
 * [dataList] 数据列表, 数据bean, 会被自动赋值给`dslAdapter.itemData`成员
 * [error] 是否有错误, 如果有错误, 将会根据已有数据量智能切换到错误情感图, 或者加载更多失败的情况
 * [page] 当前Page参数 包含请求页码, 每页请求数据量
 * [initItem] 根据`Item`的类型, 为自定义的数据结构赋值
 * */
fun <Item : DslAdapterItem, Bean> DslAdapter.loadDataEnd(
    itemClass: Class<Item>,
    dataList: List<Bean>?,
    error: Throwable?,
    page: Page,
    initItem: Item.(data: Bean) -> Unit = {}
) {
    loadDataEndIndex(itemClass, dataList, error, page) { data, _ ->
        initItem(data)
    }
}

注释写的很清楚了, 下面看一下实战代码:

dslAdapter.loadDataEnd(DslSelectPoiItem::class.java, list, null, singlePage()) { bean ->
    itemMapLocation = MapLocation.from(bean)
    itemBottomInsert = 1 * dpi
    itemDecorationColor = _color(R.color.lib_line_dark)
}

//因为demo没有加载更多, 所以使用`singlePage()`不触发`加载更多`

如此, 就结束了. 关注您该关注的, 多余的都是在浪费生命.

注意:loadDataEnd适合单类型Item的界面, 多类型可以考虑使用updateData

QA

更多使用代码, 请查看Demo源码.

如有疑问, 请直接联系本人.