背景
相信大家都已經(jīng)在使用kotlin了,可我們使用最頻繁的Adapter缺很少有人用kotlin做擴(kuò)展,即使有,但給我的感覺還是不夠,第一不夠簡(jiǎn)潔,第二功能耦合在一起,第三不夠完善,于是我決定自己做一個(gè),經(jīng)過這段時(shí)間的研究,前面也寫了三篇博客了,都是我這段時(shí)間的勞動(dòng)成果,可之前的設(shè)計(jì)還是會(huì)有一些不好的地方,也是經(jīng)過幾次的驗(yàn)證后,目前有了穩(wěn)定版,對(duì)于這個(gè)版本我還是比較滿意的,下面有請(qǐng)我厚臉皮給你們講一講
源碼地址
https://github.com/ibaozi-cn/RecyclerViewAdapter
Gradle依賴
allprojects {
repositories {
// 首先項(xiàng)目根目錄的build.gradle文件中加入這一行
maven { url 'https://jitpack.io' }
}
}
def adapterVersion = 'v1.2.0'
//核心庫
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-core:$adapterVersion"
//下面都是可選項(xiàng)
//anko layout 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-anko:$adapterVersion"
//diffutil 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-diff:$adapterVersion"
//data binding擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-binding:$adapterVersion"
// paging3 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-paging:$adapterVersion"
// sortedlist 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-sorted:$adapterVersion"
// flexbox 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-flex:$adapterVersion"
// UI 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-ui:$adapterVersion"
// Selectable 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-selectable:$adapterVersion"
// Expandable 擴(kuò)展
implementation "com.github.ibaozi-cn.RecyclerViewAdapter:adapter-expandable:$adapterVersion"
當(dāng)前版本庫大小
| 名字 | release aar size | 其他 |
|---|---|---|
| Core | 28kb | 核心庫目前包含ListAdapter的實(shí)現(xiàn),最基礎(chǔ)且最實(shí)用的擴(kuò)展 |
| Anko | 13kb | 適用本項(xiàng)目所有Adapter擴(kuò)展 |
| DataBinding | 20kb | 適配DataBinding布局,適用本項(xiàng)目所有Adapter擴(kuò)展 |
| Sorted | 10kb | SortedListAdapter擴(kuò)展實(shí)現(xiàn) |
| Paging | 13kb | PagingListAdapter擴(kuò)展適配 |
| Diff | 6kb | 適配DiffUtil,目前適用ListAdapter |
| FlexBox | 9kb | 適配FlexBox布局 |
| Selectable | 8kb | 動(dòng)態(tài)擴(kuò)展單選、多選、最大可選項(xiàng)功能 |
| Expandable | 8kb | 動(dòng)態(tài)擴(kuò)展可展開功能,支持僅單展開或多展開配置 |
| UI | 17kb | 擴(kuò)展空布局 |
對(duì)Adapter擴(kuò)展類圖
上面的內(nèi)容我大致描述一下
- IAdapter 最底層的抽象接口
- ViewHolderCacheAdapter 統(tǒng)一處理ViewHolder的緩存和ViewModel的回調(diào)
- ListAdapter 擴(kuò)展ViewHolderCacheAdapter,實(shí)現(xiàn)對(duì)ArrayList數(shù)據(jù)結(jié)構(gòu)的處理
- SortedListAdapter 擴(kuò)展ViewHolderCacheAdapter,實(shí)現(xiàn)對(duì)SortedList數(shù)據(jù)結(jié)構(gòu)的處理
- PagingListAdapter 擴(kuò)展ViewHolderCacheAdapter,實(shí)現(xiàn)對(duì)AsyncPagingDataDiffer的處理
- IAdapter Expandable 動(dòng)態(tài)擴(kuò)展 這里用的Kotlin的擴(kuò)展函數(shù)實(shí)現(xiàn),類似組合繼承,解耦方便
- IAdapter Selectable 動(dòng)態(tài)擴(kuò)展 同上
- ViewModel 這個(gè)是對(duì)Adapter每一個(gè)Item的高度抽象,負(fù)責(zé)配置Model數(shù)據(jù),負(fù)責(zé)獲取ViewHolder的實(shí)例,緩存itemViewType等
- DefaultViewModel 負(fù)責(zé)創(chuàng)建DefaultViewHolder,并提供ViewHolder初始化InitView的回調(diào),這個(gè)很關(guān)鍵,這是我們可以直接在ViewModelDSl中findView的關(guān)鍵
- LayoutViewModel 擴(kuò)展自DefaultViewModel,實(shí)現(xiàn)LayoutInflater加載View
- AnkoViewModel 擴(kuò)展自DefaultViewModel,實(shí)現(xiàn)AnkoComponent加載View
- BindingViewModel 擴(kuò)展自DefaultViewModel,實(shí)現(xiàn)DataBindingUtil加載View和ViewDataBinding綁定數(shù)據(jù)
- WrapAdapter 專門負(fù)責(zé)裝飾老的Adapter適配器,一種很好的設(shè)計(jì)模式,比起繼承實(shí)現(xiàn)要好太多了,EmptyAdapter就是一個(gè)很好的實(shí)現(xiàn),只需要將以前的Adapter包裹一層EmptyAdapter,就可以輕松實(shí)現(xiàn)空布局,這里我想啰嗦一句,如果是頭尾布局,不建議這么做,我們RecyclerView升級(jí)1.2.0以后就會(huì)有一個(gè)ConcatAdapter,這個(gè)適配器才是我們實(shí)現(xiàn)頭尾布局的神器,期待ing
這么好的框架如何使用呢?
源碼中提供全面的例子,Demo目錄如圖哦
一些效果圖
下載源碼,跑一下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 數(shù)據(jù)
layoutViewModelDsl(R.layout.item_test, ModelTest("title", "subTitle")) {
// 初始化 View,這里只會(huì)調(diào)用一次哦,放心初始化,放心的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數(shù)據(jù)
vm?.model?.title = "測(cè)試更新${Random.nextInt(10000)}"
//用Adapter更新數(shù)據(jù)
getAdapter<ListAdapter>()?.set(adapterPosition, vm)
}
//數(shù)據(jù)觸發(fā)更新的時(shí)候,綁定新的Model數(shù)據(jù)
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對(duì)象
ankoViewModelDsl(ModelTest("title", "ankoViewModelDsl"), { AnkoItemView() }) {
//數(shù)據(jù)更新
onBindViewHolder { _ ->
val model = getModel<ModelTest>()
val ankoView = getAnkoView<AnkoItemView>()
ankoView?.tvTitle?.text = model?.title
ankoView?.tvSubTitle?.text = model?.subTitle
}
//點(diǎn)擊事件處理
itemView.setOnClickListener {
val viewModel = getViewModel<AnkoViewModel<ModelTest, AnkoItemView>>()
viewModel?.model?.title = "點(diǎn)擊更新${Random.nextInt(10000)}"
getAdapter<ListAdapter>()?.set(adapterPosition, viewModel)
}
}
與LayoutViewModel的不同就在于無需在DSL中初始化View,因?yàn)橐呀?jīng)在AnkoView中做了緩存,它唯一的優(yōu)勢(shì)就是比LayoutViewModel更快的加載速度,但Anko Layout已經(jīng)不維護(hù)了,你是不是不敢用了呢?在我看來,問題不大,因?yàn)槲铱梢宰远xAnkoView,自己來做擴(kuò)展,性能的提升遠(yuǎn)大于代碼的數(shù)量,你說呢?
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")) {
//設(shè)置點(diǎn)擊事件
itemView.setOnClickListener {
val viewModel = getViewModel<BindingViewModel<ModelTest>>()
viewModel?.model?.title = "${java.util.Random().nextInt(100)}"
getAdapter<ListAdapter>()?.set(adapterPosition, viewModel)
}
}
沒有findView,沒有onBindViewHolder,代碼縮減了很多,如果你追求的就是高效率,請(qǐng)使用它,準(zhǔn)沒錯(cuò),三種加載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數(shù)據(jù)
vm?.model?.title = "測(cè)試更新${Random.nextInt(10000)}"
//用Adapter更新數(shù)據(jù)
getAdapter<ListAdapter>()?.set(adapterPosition, vm)
}
onBindViewHolder {
val model = getModel<ModelTest>()
titleText.text = model?.title
subTitleText.text = model?.subTitle
}
}
}
省略Anko、Binding...
如何實(shí)現(xiàn)一個(gè)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數(shù)據(jù)
(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)
//設(shè)置監(jiān)聽
itemView.setOnClickListener {
//改變選擇狀態(tài)
toggleSelection(adapterPosition) {
if (it) {
longToast("可選項(xiàng)已達(dá)到最大值")
}
}
Log.d("isMultiSelectable", "isMultiSelectable$isMultiSelect")
}
onBindViewHolder {
val model = getModel<ModelTest>()
title.text = model?.title
subTitle.text = model?.subTitle
// 獲取選擇狀態(tài),來適配不同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("設(shè)置最大可選").setOnClickListener {
//配置最大的可選擇項(xiàng)
val random = Random().nextInt(6)
btn_right.setText("設(shè)置最大可選$random")
adapter.setSelectableMaxSize(random)
}
}
}
有沒有前所未有簡(jiǎn)單呢?這就是Kotlin動(dòng)態(tài)擴(kuò)展函數(shù)帶來的便利,下面請(qǐng)看下實(shí)現(xiàn)的源碼:
//根據(jù)列表實(shí)例緩存已選擇項(xiàng),只緩存選中的,未選中的會(huì)被清理掉節(jié)省內(nèi)存,用弱引用來提高內(nèi)存回收率
private val selectedItemsCache = SparseArray<WeakReference<SparseBooleanArray?>?>()
private val selectConfigCache = SparseArray<WeakReference<SparseArray<Any>?>?>()
//可選項(xiàng)默認(rèn)配置
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
}
}
// 獲取選擇項(xiàng)配置信息
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
}
}
// 動(dòng)態(tài)擴(kuò)展IAdapter 判斷當(dāng)前是否多選
var IAdapter<*>.isMultiSelect
get() = getSelectConfig(hashCode())[0] as Boolean
private set(value) {}
// 動(dòng)態(tài)擴(kuò)展IAdapter 獲取最大可選擇數(shù)
var IAdapter<*>.selectedMaxSize: Int
get() = getSelectConfig(hashCode())[1] as Int
private set(value) {}
// 動(dòng)態(tài)擴(kuò)展IAdapter 獲取已選擇項(xiàng)的大小
var IAdapter<*>.selectedCount: Int
get() = getSelectedItems(hashCode()).size()
private set(value) {}
// 動(dòng)態(tài)擴(kuò)展IAdapter 配置多選和單選的狀態(tài)
fun IAdapter<*>.setMultiSelectable(enable: Boolean) {
getSelectConfig(hashCode()).setValueAt(0, enable)
if (!enable && selectedCount > 1) {
clearSelection()
}
}
// 動(dòng)態(tài)擴(kuò)展IAdapter 配置最大可選擇數(shù)
fun IAdapter<*>.setSelectableMaxSize(size: Int) {
getSelectConfig(hashCode()).setValueAt(1, size)
}
// 動(dòng)態(tài)擴(kuò)展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
}
// 動(dòng)態(tài)擴(kuò)展IAdapter 判斷當(dāng)前是否已選擇
fun IAdapter<*>.isSelected(position: Int) = getSelectedItems().contains(position)
// 動(dòng)態(tài)擴(kuò)展IAdapter 清空已選擇項(xiàng)
fun IAdapter<*>.clearSelection() {
val selection = getSelectedItems()
getSelectedItems(hashCode()).clear()
for (i in selection) {
notifyItemChanged(i)
}
}
//動(dòng)態(tài)擴(kuò)展IAdapter 改變選擇狀態(tài)
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)
}
沒有繼承,沒有組合,就是動(dòng)態(tài)擴(kuò)展,這樣的解耦方式,是不是比以前更加的好用呢?我認(rèn)為還可以,不知道你怎么想,歡迎留言。Expandable實(shí)現(xiàn)原理同上,就不再描述了哦
WrapAdapter的實(shí)現(xiàn)
為什么要有WrapAdapter?還是以前的例子,來看下那個(gè)截圖
看到?jīng)],這里面就有一個(gè)EMPTY_VIEW,不光這些還有頭尾布局,這樣的邏輯你敢用嗎?如果有了WrapAdapter是什么樣子呢?
override fun getItemViewType(position: Int): Int {
return if (displayEmptyView(emptyState)) {
viewModel.itemViewType
} else {
super.getItemViewType(position)
}
}
就這樣就行了,簡(jiǎn)單明了,這么簡(jiǎn)潔的代碼你不點(diǎn)個(gè)贊嗎?哈哈,其實(shí)這就是裝飾者模式的魅力,其實(shí)它的核心就是將真實(shí)適配器的調(diào)用權(quán)交給了WrapAdapter,然后在合適的時(shí)機(jī)再調(diào)用真實(shí)的Adapter來展示數(shù)據(jù)。其實(shí)WrapAdapter的實(shí)現(xiàn)很簡(jiǎn)單,來看下一段代碼
// 繼承自RecyclerView.Adapter 可以傳入一個(gè)新的Adapter
open class WrapAdapter<VH : RecyclerView.ViewHolder>(private var mWrappedAdapter: RecyclerView.Adapter<VH>) :
RecyclerView.Adapter<VH>()
// 注冊(cè)observer,實(shí)現(xiàn)notifyDataChange一系列相關(guān)回調(diào)
mWrappedAdapter.registerAdapterDataObserver(wrapAdapterDataObserver)
// 一些關(guān)鍵函數(shù)的調(diào)用實(shí)現(xiàn),這里沒寫全哦,詳細(xì)還請(qǐng)?zhí)朐创a查看
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
}
}
}
也是超級(jí)簡(jiǎn)單是吧,很容易就理解了對(duì)嗎?
如何使用SortedListAdapter呢?
我們先來看一段代碼
/**
* sortedId 排序用
* title 作為uniqueId ,RecyclerView ItemView 更新的時(shí)候,唯一值,注意列表是可以出現(xiàn)一樣的uniqueId的,
* 如果想更新請(qǐng)調(diào)用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中都可以做到通用的,這點(diǎn)你可以放心哦,SorteList我們都知道它是需要對(duì)數(shù)據(jù)進(jìn)行比較的,所以我們提供了SortedModel接口,你只需要實(shí)現(xiàn)SortedModel接口就可以將其放入ViewModel中,然后再放入Adapter中就行了,SortedModel實(shí)現(xiàn)SameModel,這里是接口繼承,在kotlin里面接口是可以有實(shí)現(xiàn)的,
interface SameModel {
var uniqueId: String
//是否是同一個(gè)Model
fun <T : SameModel> isSameModelAs(model: T): Boolean {
return this.uniqueId == model.uniqueId
}
//同一個(gè)Model的話,數(shù)據(jù)是否有變化
fun <T : SameModel> isContentTheSameAs(model: T): Boolean {
return this == model
}
//局部刷新時(shí)使用
fun <T : SameModel> getChangePayload(newItem: T): Any? = null
}
interface SortedModel : SameModel {
/**
* 排序使用
*/
fun <T : SortedModel> compare(model: T): Int
}
由于是繼承接口實(shí)現(xiàn),所以侵入性不高,對(duì)于一般的業(yè)務(wù)都可以適用,你可以放心大膽的使用哦。在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)
// 初始化數(shù)據(jù)
(0..10).map {
mSortedListAdapter.add(SortedItemViewModelTest().apply {
model = SortedModelTest(it, "標(biāo)題$it", "副標(biāo)題$it")
})
}
var index = 100
btn_left.setText("新增").setOnClickListener {
// 要想根據(jù)uniqueId更新數(shù)據(jù),需要調(diào)用updateItem方法
mSortedListAdapter.add(SortedItemViewModelTest().apply {
model = SortedModelTest(index++, "標(biāo)題$index", "新增$index")
})
}
btn_middle.setText("刪除").setOnClickListener {
if (mSortedListAdapter.size > 0) {
val randomInt = Random.nextInt(0, mSortedListAdapter.size)
mSortedListAdapter.removeAt(randomInt)
}
}
btn_right.setText("替換").setOnClickListener {
// 根據(jù)uniqueId替換 如果sortId不一樣就會(huì)觸發(fā)排序
if (mSortedListAdapter.size > 0) {
val randomInt = Random.nextInt(0, mSortedListAdapter.size)
mSortedListAdapter.set(randomInt, mSortedListAdapter.getItem(randomInt).also {
it as SortedItemViewModelTest
it.model?.subTitle = "替換副標(biāo)題"
})
}
}
}
}
未來更多的規(guī)劃
- 上啦加載更多,滾動(dòng)底部或頭部回調(diào)、獲取可見項(xiàng)
- 拖動(dòng)處理、滑動(dòng)刪除
- 基礎(chǔ)動(dòng)畫
- Item邊距處理
- 樹的展開擴(kuò)展,目前展開只是支持了一層,未來實(shí)現(xiàn)多層展開
- StickyHeader擴(kuò)展,列標(biāo)題實(shí)現(xiàn)
- 等等吧
這么全面的Adapter你見過幾個(gè)?還不動(dòng)動(dòng)小手關(guān)注一哈,嘿嘿,謝謝??
總結(jié)
我這期針對(duì)穩(wěn)定版本,寫的不是很多,主要就是為了讓你們知道如何使用,以及一些源碼的展示,其實(shí)我們?cè)谧鲩_發(fā)的同時(shí),真的會(huì)遇到各種各樣的列表,當(dāng)然它不能覆蓋業(yè)務(wù)中的各個(gè)場(chǎng)景,但我希望能在某些實(shí)現(xiàn)的角度上能讓你收益,用更加合理的實(shí)現(xiàn)方式來解決業(yè)務(wù)中各種各樣的復(fù)雜場(chǎng)景。
感謝
https://github.com/mikepenz/FastAdapter
https://github.com/DevAhamed/MultiViewAdapter
https://github.com/davideas/FlexibleAdapter
https://github.com/liangjingkanji/BRV
https://github.com/h6ah4i/android-advancedrecyclerview
https://github.com/evant/binding-collection-adapter
特別感謝這些優(yōu)秀設(shè)計(jì)者的項(xiàng)目,是他們的經(jīng)驗(yàn)積累,讓我有了更多的想法和實(shí)現(xiàn)。
開發(fā)者
i校長
- 簡(jiǎn)書 http://www.itdecent.cn/u/77699cd41b28
- 掘金 https://juejin.im/user/131597127135687
- 個(gè)人網(wǎng)站 http://jetpack.net.cn 、 http://ibaozi.cn