From 867e5b7e08347ba6f89ac73a4dd4f1eae2d280fd Mon Sep 17 00:00:00 2001 From: Jieyi Date: Thu, 7 Mar 2019 15:52:47 +0900 Subject: [PATCH] Feature: added the async diff comparing. --- adaptiverecyclerview/build.gradle | 1 + .../adaptiverecyclerview/AdaptiveAdapter.kt | 211 ++++++++++++------ .../DefaultMultiDiffUtil.kt | 15 ++ .../devrapid/adaptiverecyclerview/Message.kt | 11 + .../adaptiverecyclerview/MessageType.kt | 12 + build.gradle | 2 +- sample/build.gradle | 1 + .../com/devrapid/example/DeletableActivity.kt | 2 +- .../com/devrapid/example/ExpandAdapter.kt | 44 ++-- .../java/com/devrapid/example/MainActivity.kt | 14 +- 10 files changed, 216 insertions(+), 97 deletions(-) create mode 100644 adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/DefaultMultiDiffUtil.kt create mode 100644 adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/Message.kt create mode 100644 adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/MessageType.kt diff --git a/adaptiverecyclerview/build.gradle b/adaptiverecyclerview/build.gradle index 0b3fc12..1755cc8 100644 --- a/adaptiverecyclerview/build.gradle +++ b/adaptiverecyclerview/build.gradle @@ -21,6 +21,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.recyclerview:recyclerview:1.0.0' diff --git a/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/AdaptiveAdapter.kt b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/AdaptiveAdapter.kt index b25db7b..589c8d7 100644 --- a/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/AdaptiveAdapter.kt +++ b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/AdaptiveAdapter.kt @@ -5,6 +5,20 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_LIST +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_SINGLE +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_LIST +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_SINGLE +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_ALL +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_RANGE +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_SINGLE +import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_REPLACE_ALL +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.util.ArrayDeque /** * An adaptive [RecyclerView] which accepts multiple type layout. @@ -14,6 +28,9 @@ import androidx.recyclerview.widget.RecyclerView */ abstract class AdaptiveAdapter, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter() { + protected abstract var typeFactory: VT + protected abstract var dataList: MutableList + //region Header and Footer var headerEntity: M? = null set(value) { if (field == value) return // If the same, we don't do operations as the following below. @@ -56,7 +73,8 @@ abstract class AdaptiveAdapter, VH : Re } field = value } - open var diffUtil: AdaptiveDiffUtil = MultiDiffUtil() + //endregion + open var diffUtil: AdaptiveDiffUtil = DefaultMultiDiffUtil() open var useDiffUtilUpdate = true val dataItemCount: Int get() { @@ -66,23 +84,7 @@ abstract class AdaptiveAdapter, VH : Re return size } - - protected abstract var typeFactory: VT - protected abstract var dataList: MutableList - - inner class MultiDiffUtil : AdaptiveDiffUtil() { - override var oldList = mutableListOf() - override var newList = mutableListOf() - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode() - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true - } + private val queue = ArrayDeque>() //region Necessary override methods. override fun getItemCount() = dataList.size @@ -102,39 +104,98 @@ abstract class AdaptiveAdapter, VH : Re fun listDescription() = dataList.joinToString("\n") { it.toString() } - open fun appendList(list: MutableList) { + open fun append(list: MutableList) { + queue.add(Message().also { + it.type = MESSAGE_APPEND_LIST + it.newList = list + }) + runUpdateTask() + } + + open fun append(item: M) { + queue.add(Message().also { + it.type = MESSAGE_APPEND_SINGLE + it.newItem = item + }) + runUpdateTask() + } + + open fun add(position: Int, list: MutableList) { + queue.add(Message().also { + it.type = MESSAGE_ADD_LIST + it.position = position + it.newList = list + }) + runUpdateTask() + } + + open fun add(position: Int, item: M) { + queue.add(Message().also { + it.type = MESSAGE_ADD_SINGLE + it.newItem = item + }) + runUpdateTask() + } + + open fun dropRange(range: IntRange) { + queue.add(Message().also { + it.type = MESSAGE_DROP_RANGE + it.range = range + }) + runUpdateTask() + } + + open fun dropAt(index: Int) { + queue.add(Message().also { + it.type = MESSAGE_DROP_SINGLE + it.position = index + }) + runUpdateTask() + } + + open fun clearList(header: Boolean = true, footer: Boolean = true) { + queue.add(Message().also { + it.type = MESSAGE_DROP_ALL + it.header = header + it.footer = footer + }) + runUpdateTask() + } + + open fun replaceWholeList(newList: MutableList) { + queue.add(Message().also { + it.type = MESSAGE_REPLACE_ALL + it.newList = newList + }) + runUpdateTask() + } + + //region Inner operations + protected open fun _append(list: MutableList): MutableList { var startIndex = dataList.size if (footerEntity != null) startIndex-- // [toMutableList()] will create a new [ArrayList]. - val newList = dataList.toMutableList().apply { addAll(startIndex, list) } - updateList { newList } + return dataList.toMutableList().apply { addAll(startIndex, list) } } - open fun append(item: M) { - val newList = dataList.toMutableList().apply { - if (footerEntity != null) add(dataList.size - 1, item) else add(item) - } - updateList { newList } + protected open fun _append(item: M) = dataList.toMutableList().apply { + if (footerEntity != null) add(dataList.size - 1, item) else add(item) } - open fun add(position: Int, item: M) { + protected open fun _add(position: Int, list: MutableList) = dataList.toMutableList().apply { + addAll(position + (if (headerEntity == null) 0 else 1), list) + } + + protected open fun _add(position: Int, item: M): MutableList { if (dataItemCount <= 0) throw IndexOutOfBoundsException() - val newList = dataList.toMutableList().apply { + return dataList.toMutableList().apply { add(position + (if (headerEntity == null) 0 else 1), item) } - updateList { newList } - } - - open fun add(position: Int, list: MutableList) { - val newList = dataList.toMutableList().apply { - addAll(position + (if (headerEntity == null) 0 else 1), list) - } - updateList { newList } } - open fun dropRange(range: IntRange) { + protected open fun _dropRange(range: IntRange): MutableList { var start = range.start when { @@ -148,20 +209,22 @@ abstract class AdaptiveAdapter, VH : Re // Count the range. if (headerEntity != null) start++ repeat(range.count()) { newList.removeAt(start) } - updateList { newList } - } - open fun dropAt(index: Int) { - dropRange(index..index) + return newList } - open fun clearList(header: Boolean = true, footer: Boolean = true): Boolean { - if (header) headerEntity = null - if (footer) footerEntity = null + protected open fun _dropAt(index: Int) = _dropRange(index..index) - dropRange(0..(dataItemCount - 1)) + protected open fun _clearList(header: Boolean = true, footer: Boolean = true): MutableList = runBlocking { + withContext(Dispatchers.Main) { + if (header) headerEntity = null + if (footer) footerEntity = null + } - return true + mutableListOf().apply { + headerEntity?.let(::add) + footerEntity?.let(::add) + } } /** @@ -170,25 +233,47 @@ abstract class AdaptiveAdapter, VH : Re * * @param newList */ - open fun replaceWholeList(newList: MutableList) { - val withHeaderAndFooterList = newList.toMutableList().apply { - headerEntity?.let { add(0, it) } - footerEntity?.let { add(newList.size, it) } - } - updateList { withHeaderAndFooterList } + protected open fun _replaceWholeList(newList: MutableList) = newList.toMutableList().apply { + headerEntity?.let { add(0, it) } + footerEntity?.let { add(newList.size + (if (headerEntity == null) 0 else 1), it) } + } + //endregion + + //region Real doing update task + private fun runUpdateTask() { + if (queue.size > 1) return + update(queue.peek()) } - open fun updateList(getNewListBlock: () -> MutableList) { - val newList = getNewListBlock() - val res = DiffUtil.calculateDiff(diffUtil.apply { - oldList = dataList - this.newList = newList - }) + private fun update(message: Message) { + GlobalScope.launch { + val list = extractUpdateList(message) + val res = DiffUtil.calculateDiff(diffUtil.apply { + oldList = dataList + newList = list + }) + + withContext(Dispatchers.Main) { + dataList = list + res.dispatchUpdatesTo(this@AdaptiveAdapter) + queue.remove() + // Check the queue is still having message. + if (queue.size > 0) + update(queue.peek()) + } + } + } - dataList = newList - if (useDiffUtilUpdate) - res.dispatchUpdatesTo(this) - else - notifyDataSetChanged() + private fun extractUpdateList(message: Message) = when (message.type) { + MESSAGE_APPEND_LIST -> _append(message.newList) + MESSAGE_APPEND_SINGLE -> _append(requireNotNull(message.newItem)) + MESSAGE_ADD_LIST -> _add(message.position, message.newList) + MESSAGE_ADD_SINGLE -> _add(message.position, requireNotNull(message.newItem)) + MESSAGE_DROP_RANGE -> _dropRange(message.range) + MESSAGE_DROP_SINGLE -> _dropAt(message.position) + MESSAGE_DROP_ALL -> _clearList(message.header, message.footer) + MESSAGE_REPLACE_ALL -> _replaceWholeList(message.newList) + else -> mutableListOf() } + //endregion } diff --git a/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/DefaultMultiDiffUtil.kt b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/DefaultMultiDiffUtil.kt new file mode 100644 index 0000000..a656f17 --- /dev/null +++ b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/DefaultMultiDiffUtil.kt @@ -0,0 +1,15 @@ +package com.devrapid.adaptiverecyclerview + +internal class DefaultMultiDiffUtil> : AdaptiveDiffUtil() { + override var oldList = mutableListOf() + override var newList = mutableListOf() + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode() + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true +} diff --git a/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/Message.kt b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/Message.kt new file mode 100644 index 0000000..66d9397 --- /dev/null +++ b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/Message.kt @@ -0,0 +1,11 @@ +package com.devrapid.adaptiverecyclerview + +internal class Message { + var type = -1 + var position = -1 + var range = -1..-1 + var newList = mutableListOf() + var newItem: M? = null + var header = true + var footer = true +} diff --git a/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/MessageType.kt b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/MessageType.kt new file mode 100644 index 0000000..ca316b5 --- /dev/null +++ b/adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/MessageType.kt @@ -0,0 +1,12 @@ +package com.devrapid.adaptiverecyclerview + +object MessageType { + internal const val MESSAGE_APPEND_LIST = 1 + internal const val MESSAGE_APPEND_SINGLE = 2 + internal const val MESSAGE_ADD_LIST = 3 + internal const val MESSAGE_ADD_SINGLE = 4 + internal const val MESSAGE_DROP_RANGE = 5 + internal const val MESSAGE_DROP_SINGLE = 6 + internal const val MESSAGE_DROP_ALL = 7 + internal const val MESSAGE_REPLACE_ALL = 8 +} diff --git a/build.gradle b/build.gradle index f39e776..264c194 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/sample/build.gradle b/sample/build.gradle index 806fd1b..867c9b0 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -22,6 +22,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.recyclerview:recyclerview:1.0.0' diff --git a/sample/src/main/java/com/devrapid/example/DeletableActivity.kt b/sample/src/main/java/com/devrapid/example/DeletableActivity.kt index 172cf0f..3f2a163 100644 --- a/sample/src/main/java/com/devrapid/example/DeletableActivity.kt +++ b/sample/src/main/java/com/devrapid/example/DeletableActivity.kt @@ -26,7 +26,7 @@ class DeletableActivity : AppCompatActivity() { Person("Grape"), Person("Airbnb"), Person("Jieyi")) - val adapter = ExpandAdapter().apply { appendList(itemList) } + val adapter = ExpandAdapter().apply { add(0, itemList) } ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(UP or DOWN, LEFT or RIGHT) { override fun onMove( diff --git a/sample/src/main/java/com/devrapid/example/ExpandAdapter.kt b/sample/src/main/java/com/devrapid/example/ExpandAdapter.kt index c9926db..ec00702 100644 --- a/sample/src/main/java/com/devrapid/example/ExpandAdapter.kt +++ b/sample/src/main/java/com/devrapid/example/ExpandAdapter.kt @@ -34,37 +34,37 @@ class ExpandAdapter : AdaptiveAdapter) } - } +// this.updateList { +// val subList = this.dataList[newIndex].let { +// this.changeVisibleChildNumber(position, it.childItemList.size) +// it.isExpandable = false +// it.childItemList +// } +// ArrayList(dataList).toMutableList().apply { addAll(newIndex + 1, subList as Collection) } +// } } fun collapse(position: Int, newIndex: Int) { - this.updateList { - val subList = this.dataList[newIndex].let { - this.changeVisibleChildNumber(position, 0) - it.isExpandable = true - it.childItemList - } - ArrayList(dataList).toMutableList().apply { subList(newIndex + 1, newIndex + 1 + subList.size).clear() } - } +// this.updateList { +// val subList = this.dataList[newIndex].let { +// this.changeVisibleChildNumber(position, 0) +// it.isExpandable = true +// it.childItemList +// } +// ArrayList(dataList).toMutableList().apply { subList(newIndex + 1, newIndex + 1 + subList.size).clear() } +// } } fun calculateIndex(oldPos: Int): Int = (0..(oldPos - 1)).sumBy { this.originalParentPosition[it] } + oldPos fun isCollapsed(position: Int): Boolean = this.dataList[position].isExpandable - override fun updateList(getNewListBlock: () -> MutableList) { - val newList = getNewListBlock() - val res = DiffUtil.calculateDiff(ExpandDiffUtil(this.dataList, newList), true) - this.dataList = newList - res.dispatchUpdatesTo(this) - } +// override fun updateList(getNewListBlock: () -> MutableList) { +// val newList = getNewListBlock() +// val res = DiffUtil.calculateDiff(ExpandDiffUtil(this.dataList, newList), true) +// this.dataList = newList +// res.dispatchUpdatesTo(this) +// } private fun changeVisibleChildNumber(index: Int, size: Int) { this.originalParentPosition[index] = size diff --git a/sample/src/main/java/com/devrapid/example/MainActivity.kt b/sample/src/main/java/com/devrapid/example/MainActivity.kt index 1098c24..4994169 100644 --- a/sample/src/main/java/com/devrapid/example/MainActivity.kt +++ b/sample/src/main/java/com/devrapid/example/MainActivity.kt @@ -27,29 +27,23 @@ class MainActivity : AppCompatActivity() { Person("Airbnb"), Person("Jieyi")) + recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) val adapter = ExpandAdapter().apply { - appendList(itemList) + append(itemList) } - recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) recyclerView.adapter = adapter adapter.headerEntity = Person("Google @@@@@@@@") adapter.footerEntity = Person("Google !!!!!!!!") btn_add.setOnClickListener { - adapter.add(0, mutableListOf(Person("BBBBBBBB ${a++}"), + adapter.append(mutableListOf(Person("BBBBBBBB ${a++}"), Person("BBBBBBBB ${a++}"), Person("BBBBBBBB ${a++}"), Person("BBBBBBBB ${a++}"))) } btn_minus.setOnClickListener { - // adapter.appendList(mutableListOf(Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"), -// Person("BBBBBBBB ${a++}"))) + adapter.clearList() } } }