a
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = Version.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Version.minSdk
|
||||
targetSdk = Version.targetSdk
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
compileOptions {
|
||||
targetCompatibility(JavaVersion.VERSION_11)
|
||||
sourceCompatibility(JavaVersion.VERSION_11)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(Deps.recyclerview)
|
||||
implementation(Deps.paging)
|
||||
implementation(Deps.pagingKtx)
|
||||
testImplementation(Deps.testJunit)
|
||||
androidTestImplementation(Deps.androidTestJunit)
|
||||
androidTestImplementation(Deps.androidTestEspresso)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.lowe.mylibrary.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lowe.multitype">
|
||||
|
||||
</manifest>
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* [MultiTypeAdapter] or [PagingMultiTypeAdapter] base implements, used for abstract main functions.
|
||||
*
|
||||
* @author Lowae
|
||||
*/
|
||||
abstract class BaseMultiTypeAdapter @JvmOverloads constructor(
|
||||
open val initialCapacity: Int = 0,
|
||||
open var types: Types = MutableTypes(initialCapacity),
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
/**
|
||||
* [PagingMultiTypeAdapter] need this
|
||||
*/
|
||||
abstract fun getItem(position: Int): Any
|
||||
|
||||
/**
|
||||
* Registers a type class and its item view delegate. If you have registered the class,
|
||||
* it will override the original delegate(s). Note that the method is non-thread-safe
|
||||
* so that you should not use it in concurrent operation.
|
||||
*
|
||||
* Note that the method should not be called after
|
||||
* [RecyclerView.setAdapter], or you have to call the setAdapter
|
||||
* again.
|
||||
*
|
||||
* @param clazz the class of a item
|
||||
* @param delegate the item view delegate
|
||||
* @param T the item data type
|
||||
* */
|
||||
fun <T> register(clazz: Class<T>, delegate: ItemViewBaseDelegate<T, *>) {
|
||||
unregisterAllTypesIfNeeded(clazz)
|
||||
register(Type(clazz, delegate, DefaultLinker()))
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(delegate: ItemViewBaseDelegate<T, *>) {
|
||||
register(T::class.java, delegate)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(
|
||||
// Keep this parameter to provide the explicit relationship
|
||||
@Suppress("UNUSED_PARAMETER") clazz: KClass<T>,
|
||||
delegate: ItemViewBaseDelegate<T, *>,
|
||||
) {
|
||||
// Always use the reified type to avoid javaPrimitiveType problem
|
||||
// See https://github.com/drakeet/MultiType/issues/302
|
||||
register(T::class.java, delegate)
|
||||
}
|
||||
|
||||
fun <T> register(clazz: Class<T>, binder: ItemViewBinder<T, *>) {
|
||||
register(clazz, binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
fun <T> register(clazz: Class<T>, binder: PagingItemViewBinder<T, *>) {
|
||||
register(clazz, binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(binder: ItemViewBinder<T, *>) {
|
||||
register(binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(binder: PagingItemViewBinder<T, *>) {
|
||||
register(binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(clazz: KClass<T>, binder: ItemViewBinder<T, *>) {
|
||||
register(clazz, binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> register(clazz: KClass<T>, binder: PagingItemViewBinder<T, *>) {
|
||||
register(clazz, binder as ItemViewBaseDelegate<T, *>)
|
||||
}
|
||||
|
||||
internal fun <T> register(type: Type<T>) {
|
||||
types.register(type)
|
||||
when (type.delegate) {
|
||||
is ItemViewDelegate -> {
|
||||
check(this is MultiTypeAdapter) { IllegalArgumentException("Are you register ${type.delegate} to MultiTypeAdapter correctly?") }
|
||||
type.delegate._adapter = this
|
||||
}
|
||||
is PagingItemViewDelegate -> {
|
||||
check(this is PagingMultiTypeAdapter<*>) { IllegalArgumentException("Are you register ${type.delegate} to PagingMultiTypeAdapter correctly?") }
|
||||
type.delegate._adapter = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a type class to multiple item view delegates. If you have registered the
|
||||
* class, it will override the original delegate(s). Note that the method is non-thread-safe
|
||||
* so that you should not use it in concurrent operation.
|
||||
*
|
||||
* Note that the method should not be called after
|
||||
* [RecyclerView.setAdapter], or you have to call the setAdapter again.
|
||||
*
|
||||
* @param clazz the class of a item
|
||||
* @param <T> the item data type
|
||||
* @return [OneToManyFlow] for setting the delegates
|
||||
* @see [register]
|
||||
*/
|
||||
@CheckResult
|
||||
fun <T> register(clazz: Class<T>): OneToManyFlow<T> {
|
||||
unregisterAllTypesIfNeeded(clazz)
|
||||
return OneToManyBuilder(this, clazz)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun <T : Any> register(clazz: KClass<T>): OneToManyFlow<T> {
|
||||
return register(clazz.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all of the contents in the specified [Types]. If you have registered a
|
||||
* class, it will override the original delegate(s). Note that the method is non-thread-safe
|
||||
* so that you should not use it in concurrent operation.
|
||||
*
|
||||
* Note that the method should not be called after
|
||||
* [RecyclerView.setAdapter], or you have to call the setAdapter
|
||||
* again.
|
||||
*
|
||||
* @param types a [Types] containing contents to be added to this adapter inner [Types]
|
||||
* @see [register]
|
||||
* @see [register]
|
||||
*/
|
||||
fun registerAll(types: Types) {
|
||||
val size = types.size
|
||||
for (i in 0 until size) {
|
||||
val type = types.getType<Any>(i)
|
||||
unregisterAllTypesIfNeeded(type.clazz)
|
||||
register(type)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return indexInTypesOf(position, getItem(position))
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
indexViewType: Int
|
||||
): RecyclerView.ViewHolder {
|
||||
return types.getType<Any>(indexViewType).delegate.onCreateViewHolder(parent.context, parent)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
onBindViewHolder(holder, position, emptyList())
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: RecyclerView.ViewHolder,
|
||||
position: Int,
|
||||
payloads: List<Any>
|
||||
) {
|
||||
getOutDelegateByViewHolder(holder).onBindViewHolder(holder, getItem(position), payloads)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to return the stable ID for the item, and passes the event to its associated delegate.
|
||||
*
|
||||
* @param position Adapter position to query
|
||||
* @return the stable ID of the item at position
|
||||
* @see ItemViewBaseDelegate.getItemId
|
||||
* @see RecyclerView.Adapter.setHasStableIds
|
||||
* @since v3.2.0
|
||||
*/
|
||||
override fun getItemId(position: Int): Long {
|
||||
val itemViewType = getItemViewType(position)
|
||||
return types.getType<Any>(itemViewType).delegate.getItemId(getItem(position))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a view created by this adapter has been recycled, and passes the event to its
|
||||
* associated delegate.
|
||||
*
|
||||
* @param holder The ViewHolder for the view being recycled
|
||||
* @see RecyclerView.Adapter.onViewRecycled
|
||||
* @see ItemViewBaseDelegate.onViewRecycled
|
||||
*/
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
getOutDelegateByViewHolder(holder).onViewRecycled(holder)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
|
||||
* due to its transient state, and passes the event to its associated item view delegate.
|
||||
*
|
||||
* @param holder The ViewHolder containing the View that could not be recycled due to its
|
||||
* transient state.
|
||||
* @return True if the View should be recycled, false otherwise. Note that if this method
|
||||
* returns `true`, RecyclerView *will ignore* the transient state of
|
||||
* the View and recycle it regardless. If this method returns `false`,
|
||||
* RecyclerView will check the View's transient state again before giving a final decision.
|
||||
* Default implementation returns false.
|
||||
* @see RecyclerView.Adapter.onFailedToRecycleView
|
||||
* @see ItemViewBaseDelegate.onFailedToRecycleView
|
||||
*/
|
||||
override fun onFailedToRecycleView(holder: RecyclerView.ViewHolder): Boolean {
|
||||
return getOutDelegateByViewHolder(holder).onFailedToRecycleView(holder)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a view created by this adapter has been attached to a window, and passes the
|
||||
* event to its associated item view delegate.
|
||||
*
|
||||
* @param holder Holder of the view being attached
|
||||
* @see RecyclerView.Adapter.onViewAttachedToWindow
|
||||
* @see ItemViewBaseDelegate.onViewAttachedToWindow
|
||||
*/
|
||||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
||||
getOutDelegateByViewHolder(holder).onViewAttachedToWindow(holder)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a view created by this adapter has been detached from its window, and passes
|
||||
* the event to its associated item view delegate.
|
||||
*
|
||||
* @param holder Holder of the view being detached
|
||||
* @see RecyclerView.Adapter.onViewDetachedFromWindow
|
||||
* @see ItemViewBaseDelegate.onViewDetachedFromWindow
|
||||
*/
|
||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||
getOutDelegateByViewHolder(holder).onViewDetachedFromWindow(holder)
|
||||
}
|
||||
|
||||
private fun getOutDelegateByViewHolder(holder: RecyclerView.ViewHolder): ItemViewBaseDelegate<Any, RecyclerView.ViewHolder> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return types.getType<Any>(holder.itemViewType).delegate as ItemViewBaseDelegate<Any, RecyclerView.ViewHolder>
|
||||
}
|
||||
|
||||
private fun getOutDelegateByViewHolder(position: Int): ItemViewBaseDelegate<Any, RecyclerView.ViewHolder> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return types.getType<Any>(getItemViewType(position)).delegate as ItemViewBaseDelegate<Any, RecyclerView.ViewHolder>
|
||||
}
|
||||
|
||||
@Throws(DelegateNotFoundException::class)
|
||||
internal fun indexInTypesOf(position: Int, item: Any): Int {
|
||||
val index = types.firstIndexOf(item.javaClass)
|
||||
if (index != -1) {
|
||||
val linker = types.getType<Any>(index).linker
|
||||
return index + linker.index(position, item)
|
||||
}
|
||||
throw DelegateNotFoundException(item.javaClass)
|
||||
}
|
||||
|
||||
private fun unregisterAllTypesIfNeeded(clazz: Class<*>) {
|
||||
if (types.unregister(clazz)) {
|
||||
Log.w(TAG, "The type ${clazz.simpleName} you originally registered is now overwritten.")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MultiTypeAdapter"
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
internal class ClassLinkerBridge<T> private constructor(
|
||||
private val javaClassLinker: JavaClassLinker<T>,
|
||||
private val delegates: Array<ItemViewDelegate<T, *>>
|
||||
) : Linker<T> {
|
||||
|
||||
override fun index(position: Int, item: T): Int {
|
||||
val indexedClass = javaClassLinker.index(position, item)
|
||||
val index = delegates.indexOfFirst { it.javaClass == indexedClass }
|
||||
if (index != -1) return index
|
||||
throw IndexOutOfBoundsException(
|
||||
"The delegates'(${delegates.contentToString()}) you registered do not contain this ${indexedClass.name}."
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T> toLinker(
|
||||
javaClassLinker: JavaClassLinker<T>,
|
||||
delegates: Array<ItemViewDelegate<T, *>>
|
||||
): Linker<T> {
|
||||
return ClassLinkerBridge(javaClassLinker, delegates)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
internal class DefaultLinker<T> : Linker<T> {
|
||||
override fun index(position: Int, item: T): Int = 0
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
internal class DelegateNotFoundException(clazz: Class<*>) : RuntimeException(
|
||||
"Have you registered the ${clazz.name} type and its delegate or binder?"
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* Footer ItemBinder
|
||||
*
|
||||
* @author Lowae
|
||||
*/
|
||||
abstract class FooterStateItemBinder<VH : RecyclerView.ViewHolder> :
|
||||
ItemViewBaseDelegate<androidx.paging.LoadState, VH>()
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* Header ItemBinder
|
||||
*
|
||||
* @author Lowae
|
||||
*/
|
||||
abstract class HeaderStateItemBinder<VH : RecyclerView.ViewHolder> :
|
||||
ItemViewBaseDelegate<androidx.paging.LoadState, VH>()
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
|
||||
abstract class ItemViewBaseDelegate<T, VH : ViewHolder> {
|
||||
|
||||
abstract fun onCreateViewHolder(context: Context, parent: ViewGroup): VH
|
||||
|
||||
/**
|
||||
* Called by MultiTypeAdapter to display the data with its view holder. This method should
|
||||
* update the contents of the [ViewHolder.itemView] to reflect the given item.
|
||||
*
|
||||
* If you need the position of an item later on (e.g. in a click listener), use
|
||||
* `ViewHolder#getAdapterPosition()` which will have the updated adapter position.
|
||||
*
|
||||
* Override `onBindViewHolder(ViewHolder, Object, List)` instead if your ItemViewDelegate
|
||||
* can handle efficient partial bind.
|
||||
*
|
||||
* @param holder The ViewHolder which should be updated to represent the contents of the
|
||||
* given item in the items data set.
|
||||
* @param item The item within the MultiTypeAdapter's items data set.
|
||||
*/
|
||||
abstract fun onBindViewHolder(holder: VH, item: T)
|
||||
|
||||
/**
|
||||
* Called by MultiTypeAdapter to display the data with its view holder. This method should
|
||||
* update the contents of the [ViewHolder.itemView] to reflect the given item.
|
||||
*
|
||||
* If you need the position of an item later on (e.g. in a click listener), use
|
||||
* [ViewHolder.getAdapterPosition] which will have the updated adapter position.
|
||||
*
|
||||
* Partial bind vs full bind:
|
||||
*
|
||||
* The payloads parameter is a merge list from [MultiTypeAdapter.notifyItemChanged] [MultiTypeAdapter.notifyItemRangeChanged].
|
||||
* If the payloads list is not empty, the ViewHolder is currently bound to old data and
|
||||
* ItemViewDelegate may run an efficient partial update using the payload info.
|
||||
* If the payload is empty, ItemViewDelegate must run a full bind.
|
||||
* ItemViewDelegate should not assume that the payload passed in notify methods will be
|
||||
* received by onBindViewHolder(). For example when the view is not attached to the screen,
|
||||
* the payload in notifyItemChange() will be simply dropped.
|
||||
*
|
||||
* This implementation calls the `onBindViewHolder(ViewHolder, Object)` by default.
|
||||
*
|
||||
* @param holder The ViewHolder which should be updated to represent the contents of the
|
||||
* given item in the items data set.
|
||||
* @param item The item within the MultiTypeAdapter's items data set.
|
||||
* @param payloads A non-null list of merged payloads. Can be empty list if requires full
|
||||
* update.
|
||||
* @since v2.5.0
|
||||
*/
|
||||
open fun onBindViewHolder(holder: VH, item: T, payloads: List<Any>) {
|
||||
onBindViewHolder(holder, item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stable ID for the `item`. If [RecyclerView.Adapter.hasStableIds]
|
||||
* would return false this method should return [RecyclerView.NO_ID]. The default
|
||||
* implementation of this method returns [RecyclerView.NO_ID].
|
||||
*
|
||||
* @param item The item within the MultiTypeAdapter's items data set to query
|
||||
* @return the stable ID of the item
|
||||
* @see RecyclerView.Adapter.setHasStableIds
|
||||
* @since v3.2.0
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
open fun getItemId(item: T): Long = RecyclerView.NO_ID
|
||||
|
||||
/**
|
||||
* Called when a view created by this [ItemViewDelegate] has been recycled.
|
||||
*
|
||||
* A view is recycled when a [LayoutManager] decides that it no longer
|
||||
* needs to be attached to its parent [RecyclerView]. This can be because it has
|
||||
* fallen out of visibility or a set of cached views represented by views still
|
||||
* attached to the parent RecyclerView. If an item view has large or expensive data
|
||||
* bound to it such as large bitmaps, this may be a good place to release those
|
||||
* resources.
|
||||
*
|
||||
* RecyclerView calls this method right before clearing ViewHolder's internal data and
|
||||
* sending it to RecycledViewPool.
|
||||
*
|
||||
* @param holder The ViewHolder for the view being recycled
|
||||
* @since v3.1.0
|
||||
*/
|
||||
open fun onViewRecycled(holder: VH) {}
|
||||
|
||||
/**
|
||||
* Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
|
||||
* due to its transient state. Upon receiving this callback, Adapter can clear the
|
||||
* animation(s) that effect the View's transient state and return `true` so that
|
||||
* the View can be recycled. Keep in mind that the View in question is already removed from
|
||||
* the RecyclerView.
|
||||
*
|
||||
* In some cases, it is acceptable to recycle a View although it has transient state. Most
|
||||
* of the time, this is a case where the transient state will be cleared in
|
||||
* [.onBindViewHolder] call when View is rebound to a new item.
|
||||
* For this reason, RecyclerView leaves the decision to the Adapter and uses the return
|
||||
* value of this method to decide whether the View should be recycled or not.
|
||||
*
|
||||
* Note that when all animations are created by [RecyclerView.ItemAnimator], you
|
||||
* should never receive this callback because RecyclerView keeps those Views as children
|
||||
* until their animations are complete. This callback is useful when children of the item
|
||||
* views create animations which may not be easy to implement using an [ ].
|
||||
*
|
||||
* You should *never* fix this issue by calling
|
||||
* `holder.itemView.setHasTransientState(false);` unless you've previously called
|
||||
* `holder.itemView.setHasTransientState(true);`. Each
|
||||
* `View.setHasTransientState(true)` call must be matched by a
|
||||
* `View.setHasTransientState(false)` call, otherwise, the state of the View
|
||||
* may become inconsistent. You should always prefer to end or cancel animations that are
|
||||
* triggering the transient state instead of handling it manually.
|
||||
*
|
||||
* @param holder The ViewHolder containing the View that could not be recycled due to its
|
||||
* transient state.
|
||||
* @return True if the View should be recycled, false otherwise. Note that if this method
|
||||
* returns `true`, RecyclerView *will ignore* the transient state of
|
||||
* the View and recycle it regardless. If this method returns `false`,
|
||||
* RecyclerView will check the View's transient state again before giving a final decision.
|
||||
* Default implementation returns false.
|
||||
* @since v3.1.0
|
||||
*/
|
||||
open fun onFailedToRecycleView(holder: VH): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a view created by this [ItemViewDelegate] has been attached to a window.
|
||||
*
|
||||
* This can be used as a reasonable signal that the view is about to be seen
|
||||
* by the user. If the [ItemViewDelegate] previously freed any resources in
|
||||
* [onViewDetachedFromWindow][.onViewDetachedFromWindow]
|
||||
* those resources should be restored here.
|
||||
*
|
||||
* @param holder Holder of the view being attached
|
||||
* @since v3.1.0
|
||||
*/
|
||||
open fun onViewAttachedToWindow(holder: VH) {}
|
||||
|
||||
/**
|
||||
* Called when a view created by this [ItemViewDelegate] has been detached from its
|
||||
* window.
|
||||
*
|
||||
* Becoming detached from the window is not necessarily a permanent condition;
|
||||
* the consumer of an Adapter's views may choose to cache views offscreen while they
|
||||
* are not visible, attaching and detaching them as appropriate.
|
||||
*
|
||||
* @param holder Holder of the view being detached
|
||||
* @since v3.1.0
|
||||
*/
|
||||
open fun onViewDetachedFromWindow(holder: VH) {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* This is a compatible version of [ItemViewDelegate].
|
||||
* @see ItemViewDelegate
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
abstract class ItemViewBinder<T, VH : RecyclerView.ViewHolder> : ItemViewDelegate<T, VH>() {
|
||||
|
||||
final override fun onCreateViewHolder(context: Context, parent: ViewGroup): VH {
|
||||
return onCreateViewHolder(LayoutInflater.from(context), parent)
|
||||
}
|
||||
|
||||
abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
|
||||
/***
|
||||
* @author Drakeet Xu
|
||||
* @since v4.1.0
|
||||
*/
|
||||
abstract class ItemViewDelegate<T, VH : ViewHolder> : ItemViewBaseDelegate<T, VH>(){
|
||||
|
||||
@Suppress("PropertyName")
|
||||
internal var _adapter: MultiTypeAdapter? = null
|
||||
|
||||
/**
|
||||
* Gets the associated [MultiTypeAdapter].
|
||||
* @since v2.3.4
|
||||
*/
|
||||
val adapter: MultiTypeAdapter
|
||||
get() {
|
||||
if (_adapter == null) {
|
||||
throw IllegalStateException(
|
||||
"This $this has not been attached to MultiTypeAdapter yet. " +
|
||||
"You should not call the method before registering the delegate."
|
||||
)
|
||||
}
|
||||
return _adapter!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the items of the associated [MultiTypeAdapter].
|
||||
* @see MultiTypeAdapter.items
|
||||
* @since v4.0.0
|
||||
*/
|
||||
var adapterItems: List<Any>
|
||||
get() = adapter.items
|
||||
set(value) {
|
||||
adapter.items = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* An interface to link the items and delegates by the classes of delegates.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface JavaClassLinker<T> {
|
||||
|
||||
/**
|
||||
* Returns the class of your registered delegates for your item.
|
||||
*
|
||||
* @param position The position in items
|
||||
* @param item The item
|
||||
* @return The index of your registered delegates
|
||||
* @see OneToManyEndpoint.withJavaClassLinker
|
||||
*/
|
||||
fun index(position: Int, item: T): Class<out ItemViewDelegate<T, *>>
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* An interface to link the items and delegates by the classes of delegates.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface KotlinClassLinker<T> {
|
||||
|
||||
/**
|
||||
* Returns the class of your registered delegates for your item.
|
||||
*
|
||||
* @param position The position in items
|
||||
* @param item The item
|
||||
* @return The index of your registered delegates
|
||||
* @see OneToManyEndpoint.withJavaClassLinker
|
||||
*/
|
||||
fun index(position: Int, item: T): KClass<out ItemViewDelegate<T, *>>
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.annotation.IntRange
|
||||
|
||||
/**
|
||||
* An interface to link the items and delegates by the array index.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface Linker<T> {
|
||||
|
||||
/**
|
||||
* Returns the index of your registered delegates for your item. The result should be in range of
|
||||
* `[0, one-to-multiple-delegates.length)`.
|
||||
*
|
||||
* Note: The argument of [OneToManyFlow.to] is the
|
||||
* one-to-multiple-delegates.
|
||||
*
|
||||
* @param position The position in items
|
||||
* @param item The data item
|
||||
* @return The index of your registered delegates
|
||||
* @see OneToManyFlow.to
|
||||
* @see OneToManyEndpoint.withLinker
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
fun index(position: Int, item: T): Int
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
open class MultiTypeAdapter @JvmOverloads constructor(
|
||||
/**
|
||||
* Sets and updates the items atomically and safely. It is recommended to use this method
|
||||
* to update the items with a new wrapper list or consider using [CopyOnWriteArrayList].
|
||||
*
|
||||
* Note: If you want to refresh the list views after setting items, you should
|
||||
* call [RecyclerView.Adapter.notifyDataSetChanged] by yourself.
|
||||
*
|
||||
* @since v2.4.1
|
||||
*/
|
||||
open var items: List<Any> = emptyList(),
|
||||
) : BaseMultiTypeAdapter() {
|
||||
|
||||
override fun getItem(position: Int) = items[position]
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* A TypePool implemented by an ArrayList.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
open class MutableTypes constructor(
|
||||
open val initialCapacity: Int = 0,
|
||||
open val types: MutableList<Type<*>> = ArrayList(initialCapacity)
|
||||
) : Types {
|
||||
|
||||
override val size: Int get() = types.size
|
||||
|
||||
override fun <T> register(type: Type<T>) {
|
||||
types.add(type)
|
||||
}
|
||||
|
||||
override fun unregister(clazz: Class<*>): Boolean {
|
||||
return types.removeAll { it.clazz == clazz }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> getType(index: Int): Type<T> = types[index] as Type<T>
|
||||
|
||||
override fun firstIndexOf(clazz: Class<*>): Int {
|
||||
val index = types.indexOfFirst { it.clazz == clazz }
|
||||
if (index != -1) {
|
||||
return index
|
||||
}
|
||||
return types.indexOfFirst { it.clazz.isAssignableFrom(clazz) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
internal class OneToManyBuilder<T>(
|
||||
private val adapter: BaseMultiTypeAdapter,
|
||||
private val clazz: Class<T>
|
||||
) : OneToManyFlow<T>, OneToManyEndpoint<T> {
|
||||
|
||||
private var delegates: Array<ItemViewDelegate<T, *>>? = null
|
||||
|
||||
@SafeVarargs
|
||||
@CheckResult(suggest = "#withLinker(Linker)")
|
||||
override fun to(vararg delegates: ItemViewDelegate<T, *>) = apply {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this.delegates = delegates as Array<ItemViewDelegate<T, *>>
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@CheckResult(suggest = "#withLinker(Linker)")
|
||||
override fun to(vararg binders: ItemViewBinder<T, *>) = apply {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this.delegates = binders as Array<ItemViewDelegate<T, *>>
|
||||
}
|
||||
|
||||
override fun withLinker(linker: Linker<T>) {
|
||||
doRegister(linker)
|
||||
}
|
||||
|
||||
override fun withJavaClassLinker(javaClassLinker: JavaClassLinker<T>) {
|
||||
withLinker(ClassLinkerBridge.toLinker(javaClassLinker, delegates!!))
|
||||
}
|
||||
|
||||
private fun doRegister(linker: Linker<T>) {
|
||||
for (delegate in delegates!!) {
|
||||
adapter.register(Type(clazz, delegate, linker))
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* End-operators for one-to-many.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface OneToManyEndpoint<T> {
|
||||
|
||||
/**
|
||||
* Sets a linker to link the items and delegates by array index.
|
||||
*
|
||||
* @param linker the row linker
|
||||
* @see Linker
|
||||
*/
|
||||
fun withLinker(linker: Linker<T>)
|
||||
|
||||
fun withLinker(linker: (position: Int, item: T) -> Int) {
|
||||
withLinker(object : Linker<T> {
|
||||
override fun index(position: Int, item: T): Int {
|
||||
return linker(position, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a class linker to link the items and delegates by the class instance of delegates.
|
||||
*
|
||||
* @param javaClassLinker the class linker
|
||||
* @see JavaClassLinker
|
||||
*/
|
||||
fun withJavaClassLinker(javaClassLinker: JavaClassLinker<T>)
|
||||
|
||||
private fun withJavaClassLinker(classLinker: (position: Int, item: T) -> Class<out ItemViewDelegate<T, *>>) {
|
||||
withJavaClassLinker(object : JavaClassLinker<T> {
|
||||
override fun index(position: Int, item: T): Class<out ItemViewDelegate<T, *>> {
|
||||
return classLinker(position, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun withKotlinClassLinker(classLinker: KotlinClassLinker<T>) {
|
||||
withJavaClassLinker { position, item -> classLinker.index(position, item).java }
|
||||
}
|
||||
|
||||
fun withKotlinClassLinker(classLinker: (position: Int, item: T) -> KClass<out ItemViewDelegate<T, *>>) {
|
||||
withJavaClassLinker { position, item -> classLinker(position, item).java }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
|
||||
/**
|
||||
* Process and flow operators for one-to-many.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface OneToManyFlow<T> {
|
||||
|
||||
/**
|
||||
* Sets some item view delegates to the item type.
|
||||
*
|
||||
* @param delegates the item view delegates
|
||||
* @return end flow operator
|
||||
*/
|
||||
@CheckResult
|
||||
fun to(vararg delegates: ItemViewDelegate<T, *>): OneToManyEndpoint<T>
|
||||
|
||||
@CheckResult
|
||||
fun to(vararg delegates: ItemViewBinder<T, *>): OneToManyEndpoint<T>
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* This is a compatible version of [PagingItemViewDelegate].
|
||||
* @see PagingItemViewDelegate
|
||||
* @author Lowae
|
||||
*/
|
||||
abstract class PagingItemViewBinder<T, VH : RecyclerView.ViewHolder> : PagingItemViewDelegate<T, VH>() {
|
||||
|
||||
final override fun onCreateViewHolder(context: Context, parent: ViewGroup): VH {
|
||||
return onCreateViewHolder(LayoutInflater.from(context), parent)
|
||||
}
|
||||
|
||||
abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
|
||||
/***
|
||||
* @author Lowae
|
||||
*/
|
||||
abstract class PagingItemViewDelegate<T, VH : ViewHolder>:ItemViewBaseDelegate<T, VH>() {
|
||||
|
||||
/**
|
||||
* This is a [PagingMultiTypeAdapter] like PagingDataAdapter
|
||||
*/
|
||||
@Suppress("PropertyName")
|
||||
internal var _adapter: PagingMultiTypeAdapter<*>? = null
|
||||
|
||||
/**
|
||||
* Gets the associated [PagingMultiTypeAdapter].
|
||||
* @since v2.3.4
|
||||
*/
|
||||
val adapter: PagingMultiTypeAdapter<*>
|
||||
get() {
|
||||
if (_adapter == null) {
|
||||
throw IllegalStateException(
|
||||
"This $this has not been attached to PagingMultiTypeAdapter yet. " +
|
||||
"You should not call the method before registering the delegate."
|
||||
)
|
||||
}
|
||||
return _adapter!!
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.LoadStateAdapter
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
|
||||
/**
|
||||
* Adapter for displaying a RecyclerView item based on [LoadState], such as a loading spinner, or
|
||||
* a retry error button.
|
||||
* more @see [LoadStateAdapter]
|
||||
*
|
||||
* @param types must passed base [PagingMultiTypeAdapter.types] to share since set [ConcatAdapter.Config.isolateViewTypes] is FALSE
|
||||
*
|
||||
* @author Lowae
|
||||
*/
|
||||
open class PagingLoadStateAdapter(binder: FooterStateItemBinder<*>, override var types: Types) :
|
||||
BaseMultiTypeAdapter() {
|
||||
|
||||
init {
|
||||
register(binder)
|
||||
}
|
||||
|
||||
var loadState: LoadState = LoadState.NotLoading(endOfPaginationReached = false)
|
||||
set(loadState) {
|
||||
if (field != loadState) {
|
||||
val oldItem = displayLoadStateAsItem(field)
|
||||
val newItem = displayLoadStateAsItem(loadState)
|
||||
|
||||
if (oldItem && !newItem) {
|
||||
notifyItemRemoved(0)
|
||||
} else if (newItem && !oldItem) {
|
||||
notifyItemInserted(0)
|
||||
} else if (oldItem && newItem) {
|
||||
notifyItemChanged(0)
|
||||
}
|
||||
field = loadState
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItem(position: Int) = loadState
|
||||
|
||||
override fun getItemCount(): Int = if (displayLoadStateAsItem(loadState)) 1 else 0
|
||||
|
||||
open fun displayLoadStateAsItem(loadState: LoadState): Boolean {
|
||||
return loadState is LoadState.Loading || loadState is LoadState.Error || (loadState is LoadState.NotLoading && loadState.endOfPaginationReached)
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.paging.*
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] base class for presenting paged data from [PagingData]s in
|
||||
* a [RecyclerView].
|
||||
* more @see [PagingDataAdapter]
|
||||
*
|
||||
* @author Lowae
|
||||
*/
|
||||
class PagingMultiTypeAdapter<T : Any>(
|
||||
diffCallback: DiffUtil.ItemCallback<T>,
|
||||
mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
|
||||
workerDispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
override val initialCapacity: Int = 0,
|
||||
override var types: Types = MutableTypes(initialCapacity)
|
||||
) : BaseMultiTypeAdapter() {
|
||||
|
||||
private val differ = AsyncPagingDataDiffer(
|
||||
diffCallback = diffCallback,
|
||||
updateCallback = AdapterListUpdateCallback(this),
|
||||
mainDispatcher = mainDispatcher,
|
||||
workerDispatcher = workerDispatcher
|
||||
)
|
||||
|
||||
override fun setHasStableIds(hasStableIds: Boolean) =
|
||||
throw UnsupportedOperationException("Stable ids are unsupported on PagingDataAdapter.")
|
||||
|
||||
override fun getItem(@IntRange(from = 0) position: Int) = differ.getItem(position) as Any
|
||||
|
||||
override fun getItemCount() = differ.itemCount
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
suspend fun submitData(pagingData: PagingData<*>) =
|
||||
differ.submitData(pagingData as PagingData<T>)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun submitData(lifecycle: Lifecycle, pagingData: PagingData<*>) =
|
||||
differ.submitData(lifecycle, pagingData as PagingData<T>)
|
||||
|
||||
fun retry() = differ.retry()
|
||||
|
||||
fun refresh() = differ.refresh()
|
||||
|
||||
fun peek(@IntRange(from = 0) index: Int) = differ.peek(index)
|
||||
|
||||
fun snapshot(): ItemSnapshotList<T> = differ.snapshot()
|
||||
|
||||
val loadStateFlow: Flow<CombinedLoadStates> = differ.loadStateFlow
|
||||
|
||||
val onPagesUpdatedFlow: Flow<Unit> = differ.onPagesUpdatedFlow
|
||||
|
||||
fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) =
|
||||
differ.addLoadStateListener(listener)
|
||||
|
||||
fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) =
|
||||
differ.removeLoadStateListener(listener)
|
||||
|
||||
fun addOnPagesUpdatedListener(listener: () -> Unit) = differ.addOnPagesUpdatedListener(listener)
|
||||
|
||||
fun removeOnPagesUpdatedListener(listener: () -> Unit) =
|
||||
differ.removeOnPagesUpdatedListener(listener)
|
||||
|
||||
fun withLoadStateHeader(
|
||||
headerStateAdapter: PagingLoadStateAdapter,
|
||||
header: HeaderStateItemBinder<*>
|
||||
): ConcatAdapter {
|
||||
headerStateAdapter.types = this.types
|
||||
headerStateAdapter.register(header)
|
||||
addLoadStateListener { loadStates ->
|
||||
headerStateAdapter.loadState = loadStates.prepend
|
||||
}
|
||||
return ConcatAdapter(concatAdapterConfig(), headerStateAdapter, this)
|
||||
}
|
||||
|
||||
fun withLoadStateFooter(
|
||||
footerStateAdapter: PagingLoadStateAdapter,
|
||||
): ConcatAdapter {
|
||||
addLoadStateListener { loadStates ->
|
||||
// Avoid of shown footer when adapter is empty
|
||||
if (itemCount != 0) {
|
||||
footerStateAdapter.loadState = loadStates.append
|
||||
}
|
||||
}
|
||||
return ConcatAdapter(concatAdapterConfig(), this, footerStateAdapter)
|
||||
}
|
||||
|
||||
fun withLoadStateHeaderAndFooter(
|
||||
headerStateAdapter: PagingLoadStateAdapter,
|
||||
header: HeaderStateItemBinder<*>,
|
||||
footerStateAdapter: PagingLoadStateAdapter,
|
||||
footer: FooterStateItemBinder<*>
|
||||
): ConcatAdapter {
|
||||
headerStateAdapter.types = this.types
|
||||
headerStateAdapter.register(header)
|
||||
footerStateAdapter.types = this.types
|
||||
footerStateAdapter.register(footer)
|
||||
addLoadStateListener { loadStates ->
|
||||
headerStateAdapter.loadState = loadStates.prepend
|
||||
footerStateAdapter.loadState = loadStates.append
|
||||
}
|
||||
return ConcatAdapter(concatAdapterConfig(), headerStateAdapter, this, footerStateAdapter)
|
||||
}
|
||||
|
||||
private fun concatAdapterConfig() = ConcatAdapter.Config.Builder().setIsolateViewTypes(false)
|
||||
.setStableIdMode(ConcatAdapter.Config.StableIdMode.NO_STABLE_IDS).build()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
data class Type<T>(
|
||||
val clazz: Class<out T>,
|
||||
val delegate: ItemViewBaseDelegate<T, *>,
|
||||
val linker: Linker<T>
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present. Drakeet Xu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.lowe.multitype
|
||||
|
||||
/**
|
||||
* An ordered collection to hold the types, delegates and linkers.
|
||||
*
|
||||
* @author Drakeet Xu
|
||||
*/
|
||||
interface Types {
|
||||
|
||||
/**
|
||||
* Returns the size of the [Types].
|
||||
*/
|
||||
val size: Int
|
||||
|
||||
fun <T> register(type: Type<T>)
|
||||
|
||||
/**
|
||||
* Unregister all types indexed by the specified class.
|
||||
*
|
||||
* @param clazz the main class of a [Type]
|
||||
* @return true if any types are unregistered from this [Types]
|
||||
*/
|
||||
fun unregister(clazz: Class<*>): Boolean
|
||||
|
||||
/**
|
||||
* Gets the type at the specified index.
|
||||
*
|
||||
* @param index the type index
|
||||
* @return the [Type] at the specified index
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
*/
|
||||
fun <T> getType(index: Int): Type<T>
|
||||
|
||||
/**
|
||||
* For getting index of the type class. If the subclass is already registered,
|
||||
* the registered mapping is used. If the subclass is not registered, then look
|
||||
* for its parent class if is registered, if the parent class is registered,
|
||||
* the subclass is regarded as the parent class.
|
||||
*
|
||||
* @param clazz the type class.
|
||||
* @return The index of the first occurrence of the specified class in this [Types],
|
||||
* or -1 if this [Types] does not contain the class.
|
||||
*/
|
||||
fun firstIndexOf(clazz: Class<*>): Int
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="tagViewHolder" type="id" />
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.lowe.multitype
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user