parent
378d2e56d1
commit
08d0a46204
@ -0,0 +1,28 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2 |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/8/20 17:13 |
||||||
|
*/ |
||||||
|
|
||||||
|
import android.view.View |
||||||
|
|
||||||
|
/** |
||||||
|
* Registers the [block] lambda as [View.OnClickListener] to this View. |
||||||
|
* |
||||||
|
* If this View is not clickable, it becomes clickable. |
||||||
|
*/ |
||||||
|
inline fun View.onClick(crossinline block: () -> Unit) = setOnClickListener { block() } |
||||||
|
|
||||||
|
/** |
||||||
|
* Register the [block] lambda as [View.OnLongClickListener] to this View. |
||||||
|
* By default, [consume] is set to true because it's the most common use case, but you can set it |
||||||
|
* to false. |
||||||
|
* If you want to return a value dynamically, use [View.setOnLongClickListener] instead. |
||||||
|
* |
||||||
|
* If this view is not long clickable, it becomes long clickable. |
||||||
|
*/ |
||||||
|
inline fun View.onLongClick( |
||||||
|
consume: Boolean = true, |
||||||
|
crossinline block: () -> Unit |
||||||
|
) = setOnLongClickListener { block(); consume } |
@ -0,0 +1,215 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2 |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer |
||||||
|
import androidx.recyclerview.widget.DiffUtil |
||||||
|
import androidx.recyclerview.widget.GridLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import androidx.viewbinding.ViewBinding |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/12/15. |
||||||
|
*/ |
||||||
|
@Suppress("unused", "MemberVisibilityCanBePrivate") |
||||||
|
abstract class DiffRecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) : |
||||||
|
RecyclerView.Adapter<ItemViewHolder>() { |
||||||
|
|
||||||
|
val inflater: LayoutInflater = LayoutInflater.from(context) |
||||||
|
|
||||||
|
private val asyncListDiffer: AsyncListDiffer<ITEM> by lazy { |
||||||
|
AsyncListDiffer(this, diffItemCallback).apply { |
||||||
|
addListListener { _, _ -> |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null |
||||||
|
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null |
||||||
|
|
||||||
|
var itemAnimation: ItemAnimation? = null |
||||||
|
|
||||||
|
abstract val diffItemCallback: DiffUtil.ItemCallback<ITEM> |
||||||
|
|
||||||
|
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) { |
||||||
|
itemClickListener = listener |
||||||
|
} |
||||||
|
|
||||||
|
fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) { |
||||||
|
itemLongClickListener = listener |
||||||
|
} |
||||||
|
|
||||||
|
fun bindToRecyclerView(recyclerView: RecyclerView) { |
||||||
|
recyclerView.adapter = this |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun setItems(items: List<ITEM>?) { |
||||||
|
kotlin.runCatching { |
||||||
|
asyncListDiffer.submitList(items) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun setItem(position: Int, item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
val list = ArrayList(asyncListDiffer.currentList) |
||||||
|
list[position] = item |
||||||
|
asyncListDiffer.submitList(list) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItem(item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = asyncListDiffer.currentList.indexOf(item) |
||||||
|
if (index >= 0) { |
||||||
|
asyncListDiffer.currentList[index] = item |
||||||
|
notifyItemChanged(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItem(position: Int, payload: Any) { |
||||||
|
kotlin.runCatching { |
||||||
|
val size = itemCount |
||||||
|
if (position in 0 until size) { |
||||||
|
notifyItemChanged(position, payload) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) { |
||||||
|
kotlin.runCatching { |
||||||
|
val size = itemCount |
||||||
|
if (fromPosition in 0 until size && toPosition in 0 until size) { |
||||||
|
notifyItemRangeChanged( |
||||||
|
fromPosition, |
||||||
|
toPosition - fromPosition + 1, |
||||||
|
payloads |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun isEmpty() = asyncListDiffer.currentList.isEmpty() |
||||||
|
|
||||||
|
fun isNotEmpty() = asyncListDiffer.currentList.isNotEmpty() |
||||||
|
|
||||||
|
fun getItem(position: Int): ITEM? = asyncListDiffer.currentList.getOrNull(position) |
||||||
|
|
||||||
|
fun getItems(): List<ITEM> = asyncListDiffer.currentList |
||||||
|
|
||||||
|
/** |
||||||
|
* grid 模式下使用 |
||||||
|
*/ |
||||||
|
protected open fun getSpanSize(viewType: Int, position: Int) = 1 |
||||||
|
|
||||||
|
final override fun getItemCount() = getItems().size |
||||||
|
|
||||||
|
final override fun getItemViewType(position: Int): Int { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { |
||||||
|
val holder = ItemViewHolder(getViewBinding(parent)) |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
registerListener(holder, (holder.binding as VB)) |
||||||
|
|
||||||
|
if (itemClickListener != null) { |
||||||
|
holder.itemView.setOnClickListener { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
itemClickListener?.invoke(holder, it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (itemLongClickListener != null) { |
||||||
|
holder.itemView.onLongClick { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
itemLongClickListener?.invoke(holder, it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return holder |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract fun getViewBinding(parent: ViewGroup): VB |
||||||
|
|
||||||
|
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {} |
||||||
|
|
||||||
|
open fun onCurrentListChanged() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
final override fun onBindViewHolder( |
||||||
|
holder: ItemViewHolder, |
||||||
|
position: Int, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
convert(holder, (holder.binding as VB), it, payloads) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: ItemViewHolder) { |
||||||
|
super.onViewAttachedToWindow(holder) |
||||||
|
addAnimation(holder) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { |
||||||
|
super.onAttachedToRecyclerView(recyclerView) |
||||||
|
val manager = recyclerView.layoutManager |
||||||
|
if (manager is GridLayoutManager) { |
||||||
|
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { |
||||||
|
override fun getSpanSize(position: Int): Int { |
||||||
|
return getSpanSize(getItemViewType(position), position) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun addAnimation(holder: ItemViewHolder) { |
||||||
|
itemAnimation?.let { |
||||||
|
if (it.itemAnimEnabled) { |
||||||
|
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) { |
||||||
|
startAnimation(holder, it) |
||||||
|
it.itemAnimStartPosition = holder.layoutPosition |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) { |
||||||
|
item.itemAnimation?.let { |
||||||
|
for (anim in it.getAnimators(holder.itemView)) { |
||||||
|
anim.setDuration(item.itemAnimDuration).start() |
||||||
|
anim.interpolator = item.itemAnimInterpolator |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题, |
||||||
|
* 使用getItem(holder.layoutPosition)来获取item |
||||||
|
*/ |
||||||
|
abstract fun convert( |
||||||
|
holder: ItemViewHolder, |
||||||
|
binding: VB, |
||||||
|
item: ITEM, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) |
||||||
|
|
||||||
|
/** |
||||||
|
* 注册事件 |
||||||
|
*/ |
||||||
|
abstract fun registerListener(holder: ItemViewHolder, binding: VB) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2 |
||||||
|
|
||||||
|
import android.view.animation.Interpolator |
||||||
|
import android.view.animation.LinearInterpolator |
||||||
|
import xyz.fycz.myreader.base.adapter2.animations.* |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/12/15. |
||||||
|
*/ |
||||||
|
@Suppress("unused") |
||||||
|
class ItemAnimation private constructor() { |
||||||
|
|
||||||
|
var itemAnimEnabled = false |
||||||
|
var itemAnimFirstOnly = true |
||||||
|
var itemAnimation: BaseAnimation? = null |
||||||
|
var itemAnimInterpolator: Interpolator = LinearInterpolator() |
||||||
|
var itemAnimDuration: Long = 300L |
||||||
|
var itemAnimStartPosition: Int = -1 |
||||||
|
|
||||||
|
fun interpolator(interpolator: Interpolator) = apply { |
||||||
|
itemAnimInterpolator = interpolator |
||||||
|
} |
||||||
|
|
||||||
|
fun duration(duration: Long) = apply { |
||||||
|
itemAnimDuration = duration |
||||||
|
} |
||||||
|
|
||||||
|
fun startPosition(startPos: Int) = apply { |
||||||
|
itemAnimStartPosition = startPos |
||||||
|
} |
||||||
|
|
||||||
|
fun animation(animationType: Int = NONE, animation: BaseAnimation? = null) = apply { |
||||||
|
if (animation != null) { |
||||||
|
itemAnimation = animation |
||||||
|
} else { |
||||||
|
when (animationType) { |
||||||
|
FADE_IN -> itemAnimation = AlphaInAnimation() |
||||||
|
SCALE_IN -> itemAnimation = ScaleInAnimation() |
||||||
|
BOTTOM_SLIDE_IN -> itemAnimation = SlideInBottomAnimation() |
||||||
|
LEFT_SLIDE_IN -> itemAnimation = SlideInLeftAnimation() |
||||||
|
RIGHT_SLIDE_IN -> itemAnimation = SlideInRightAnimation() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun enabled(enabled: Boolean) = apply { |
||||||
|
itemAnimEnabled = enabled |
||||||
|
} |
||||||
|
|
||||||
|
fun firstOnly(firstOnly: Boolean) = apply { |
||||||
|
itemAnimFirstOnly = firstOnly |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
const val NONE: Int = 0x00000000 |
||||||
|
/** |
||||||
|
* Use with [.openLoadAnimation] |
||||||
|
*/ |
||||||
|
const val FADE_IN: Int = 0x00000001 |
||||||
|
/** |
||||||
|
* Use with [.openLoadAnimation] |
||||||
|
*/ |
||||||
|
const val SCALE_IN: Int = 0x00000002 |
||||||
|
/** |
||||||
|
* Use with [.openLoadAnimation] |
||||||
|
*/ |
||||||
|
const val BOTTOM_SLIDE_IN: Int = 0x00000003 |
||||||
|
/** |
||||||
|
* Use with [.openLoadAnimation] |
||||||
|
*/ |
||||||
|
const val LEFT_SLIDE_IN: Int = 0x00000004 |
||||||
|
/** |
||||||
|
* Use with [.openLoadAnimation] |
||||||
|
*/ |
||||||
|
const val RIGHT_SLIDE_IN: Int = 0x00000005 |
||||||
|
|
||||||
|
fun create() = ItemAnimation() |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2 |
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import androidx.viewbinding.ViewBinding |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/11/28. |
||||||
|
*/ |
||||||
|
@Suppress("MemberVisibilityCanBePrivate") |
||||||
|
class ItemViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) |
@ -0,0 +1,452 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2 |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.SparseArray |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.DiffUtil |
||||||
|
import androidx.recyclerview.widget.GridLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import androidx.viewbinding.ViewBinding |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/11/24. |
||||||
|
* |
||||||
|
* 通用的adapter 可添加header,footer,以及不同类型item |
||||||
|
*/ |
||||||
|
@Suppress("unused", "MemberVisibilityCanBePrivate") |
||||||
|
abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) : |
||||||
|
RecyclerView.Adapter<ItemViewHolder>() { |
||||||
|
|
||||||
|
val inflater: LayoutInflater = LayoutInflater.from(context) |
||||||
|
|
||||||
|
private val headerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() } |
||||||
|
private val footerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() } |
||||||
|
|
||||||
|
private val items: MutableList<ITEM> = mutableListOf() |
||||||
|
|
||||||
|
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null |
||||||
|
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null |
||||||
|
|
||||||
|
var itemAnimation: ItemAnimation? = null |
||||||
|
|
||||||
|
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) { |
||||||
|
itemClickListener = listener |
||||||
|
} |
||||||
|
|
||||||
|
fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) { |
||||||
|
itemLongClickListener = listener |
||||||
|
} |
||||||
|
|
||||||
|
fun bindToRecyclerView(recyclerView: RecyclerView) { |
||||||
|
recyclerView.adapter = this |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = headerItems.size() |
||||||
|
headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header) |
||||||
|
notifyItemInserted(index) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = getActualItemCount() + footerItems.size() |
||||||
|
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer) |
||||||
|
notifyItemInserted(index) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun removeHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = headerItems.indexOfValue(header) |
||||||
|
if (index >= 0) { |
||||||
|
headerItems.remove(index) |
||||||
|
notifyItemRemoved(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = footerItems.indexOfValue(footer) |
||||||
|
if (index >= 0) { |
||||||
|
footerItems.remove(index) |
||||||
|
notifyItemRemoved(getActualItemCount() + index - 2) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun setItems(items: List<ITEM>?) { |
||||||
|
kotlin.runCatching { |
||||||
|
if (this.items.isNotEmpty()) { |
||||||
|
this.items.clear() |
||||||
|
} |
||||||
|
if (items != null) { |
||||||
|
this.items.addAll(items) |
||||||
|
} |
||||||
|
notifyDataSetChanged() |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun setItems(items: List<ITEM>?, itemCallback: DiffUtil.ItemCallback<ITEM>) { |
||||||
|
kotlin.runCatching { |
||||||
|
val callback = object : DiffUtil.Callback() { |
||||||
|
override fun getOldListSize(): Int { |
||||||
|
return itemCount |
||||||
|
} |
||||||
|
|
||||||
|
override fun getNewListSize(): Int { |
||||||
|
return (items?.size ?: 0) + getHeaderCount() + getFooterCount() |
||||||
|
} |
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { |
||||||
|
val oldItem = getItem(oldItemPosition - getHeaderCount()) |
||||||
|
?: return true |
||||||
|
val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) |
||||||
|
?: return true |
||||||
|
return itemCallback.areItemsTheSame(oldItem, newItem) |
||||||
|
} |
||||||
|
|
||||||
|
override fun areContentsTheSame( |
||||||
|
oldItemPosition: Int, |
||||||
|
newItemPosition: Int |
||||||
|
): Boolean { |
||||||
|
val oldItem = getItem(oldItemPosition - getHeaderCount()) |
||||||
|
?: return true |
||||||
|
val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) |
||||||
|
?: return true |
||||||
|
return itemCallback.areContentsTheSame(oldItem, newItem) |
||||||
|
} |
||||||
|
|
||||||
|
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { |
||||||
|
val oldItem = getItem(oldItemPosition - getHeaderCount()) |
||||||
|
?: return null |
||||||
|
val newItem = items?.getOrNull(newItemPosition - getHeaderCount()) |
||||||
|
?: return null |
||||||
|
return itemCallback.getChangePayload(oldItem, newItem) |
||||||
|
} |
||||||
|
} |
||||||
|
val diffResult = DiffUtil.calculateDiff(callback) |
||||||
|
if (this.items.isNotEmpty()) { |
||||||
|
this.items.clear() |
||||||
|
} |
||||||
|
if (items != null) { |
||||||
|
this.items.addAll(items) |
||||||
|
} |
||||||
|
diffResult.dispatchUpdatesTo(this) |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun setItem(position: Int, item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (position in 0 until oldSize) { |
||||||
|
this.items[position] = item |
||||||
|
notifyItemChanged(position + getHeaderCount()) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun addItem(item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (this.items.add(item)) { |
||||||
|
notifyItemInserted(oldSize + getHeaderCount()) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun addItems(position: Int, newItems: List<ITEM>) { |
||||||
|
kotlin.runCatching { |
||||||
|
if (this.items.addAll(position, newItems)) { |
||||||
|
notifyItemRangeInserted(position + getHeaderCount(), newItems.size) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun addItems(newItems: List<ITEM>) { |
||||||
|
kotlin.runCatching { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (this.items.addAll(newItems)) { |
||||||
|
if (oldSize == 0 && getHeaderCount() == 0) { |
||||||
|
notifyDataSetChanged() |
||||||
|
} else { |
||||||
|
notifyItemRangeInserted(oldSize + getHeaderCount(), newItems.size) |
||||||
|
} |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun removeItem(position: Int) { |
||||||
|
kotlin.runCatching { |
||||||
|
if (this.items.removeAt(position) != null) { |
||||||
|
notifyItemRemoved(position + getHeaderCount()) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun removeItem(item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
if (this.items.remove(item)) { |
||||||
|
notifyItemRemoved(this.items.indexOf(item) + getHeaderCount()) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun removeItems(items: List<ITEM>) { |
||||||
|
kotlin.runCatching { |
||||||
|
if (this.items.removeAll(items)) { |
||||||
|
notifyDataSetChanged() |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun swapItem(oldPosition: Int, newPosition: Int) { |
||||||
|
kotlin.runCatching { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (oldPosition in 0 until size && newPosition in 0 until size) { |
||||||
|
val srcPosition = oldPosition + getHeaderCount() |
||||||
|
val targetPosition = newPosition + getHeaderCount() |
||||||
|
Collections.swap(this.items, srcPosition, targetPosition) |
||||||
|
notifyItemMoved(srcPosition, targetPosition) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItem(item: ITEM) { |
||||||
|
kotlin.runCatching { |
||||||
|
val index = this.items.indexOf(item) |
||||||
|
if (index >= 0) { |
||||||
|
this.items[index] = item |
||||||
|
notifyItemChanged(index) |
||||||
|
} |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItem(position: Int, payload: Any) { |
||||||
|
kotlin.runCatching { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (position in 0 until size) { |
||||||
|
notifyItemChanged(position + getHeaderCount(), payload) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) { |
||||||
|
kotlin.runCatching { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (fromPosition in 0 until size && toPosition in 0 until size) { |
||||||
|
notifyItemRangeChanged( |
||||||
|
fromPosition + getHeaderCount(), |
||||||
|
toPosition - fromPosition + 1, |
||||||
|
payloads |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
fun clearItems() { |
||||||
|
kotlin.runCatching { |
||||||
|
this.items.clear() |
||||||
|
notifyDataSetChanged() |
||||||
|
onCurrentListChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun isEmpty() = items.isEmpty() |
||||||
|
|
||||||
|
fun isNotEmpty() = items.isNotEmpty() |
||||||
|
|
||||||
|
/** |
||||||
|
* 除去header和footer |
||||||
|
*/ |
||||||
|
fun getActualItemCount() = items.size |
||||||
|
|
||||||
|
|
||||||
|
fun getHeaderCount() = headerItems.size() |
||||||
|
|
||||||
|
|
||||||
|
fun getFooterCount() = footerItems.size() |
||||||
|
|
||||||
|
fun getItem(position: Int): ITEM? = items.getOrNull(position) |
||||||
|
|
||||||
|
fun getItemByLayoutPosition(position: Int) = items.getOrNull(position - getHeaderCount()) |
||||||
|
|
||||||
|
fun getItems(): List<ITEM> = items |
||||||
|
|
||||||
|
protected open fun getItemViewType(item: ITEM, position: Int) = 0 |
||||||
|
|
||||||
|
/** |
||||||
|
* grid 模式下使用 |
||||||
|
*/ |
||||||
|
protected open fun getSpanSize(viewType: Int, position: Int) = 1 |
||||||
|
|
||||||
|
final override fun getItemCount() = getActualItemCount() + getHeaderCount() + getFooterCount() |
||||||
|
|
||||||
|
final override fun getItemViewType(position: Int) = when { |
||||||
|
isHeader(position) -> TYPE_HEADER_VIEW + position |
||||||
|
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount() |
||||||
|
else -> getItem(getActualPosition(position))?.let { |
||||||
|
getItemViewType(it, getActualPosition(position)) |
||||||
|
} ?: 0 |
||||||
|
} |
||||||
|
|
||||||
|
open fun onCurrentListChanged() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when { |
||||||
|
viewType < TYPE_HEADER_VIEW + getHeaderCount() -> { |
||||||
|
ItemViewHolder(headerItems.get(viewType).invoke(parent)) |
||||||
|
} |
||||||
|
|
||||||
|
viewType >= TYPE_FOOTER_VIEW -> { |
||||||
|
ItemViewHolder(footerItems.get(viewType).invoke(parent)) |
||||||
|
} |
||||||
|
|
||||||
|
else -> { |
||||||
|
val holder = ItemViewHolder(getViewBinding(parent)) |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
registerListener(holder, (holder.binding as VB)) |
||||||
|
|
||||||
|
if (itemClickListener != null) { |
||||||
|
holder.itemView.setOnClickListener { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
itemClickListener?.invoke(holder, it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (itemLongClickListener != null) { |
||||||
|
holder.itemView.onLongClick { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
itemLongClickListener?.invoke(holder, it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
holder |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract fun getViewBinding(parent: ViewGroup): VB |
||||||
|
|
||||||
|
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {} |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
final override fun onBindViewHolder( |
||||||
|
holder: ItemViewHolder, |
||||||
|
position: Int, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) { |
||||||
|
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) { |
||||||
|
getItem(holder.layoutPosition - getHeaderCount())?.let { |
||||||
|
convert(holder, (holder.binding as VB), it, payloads) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: ItemViewHolder) { |
||||||
|
super.onViewAttachedToWindow(holder) |
||||||
|
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) { |
||||||
|
addAnimation(holder) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { |
||||||
|
super.onAttachedToRecyclerView(recyclerView) |
||||||
|
val manager = recyclerView.layoutManager |
||||||
|
if (manager is GridLayoutManager) { |
||||||
|
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { |
||||||
|
override fun getSpanSize(position: Int): Int { |
||||||
|
return getSpanSize(getItemViewType(position), position) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun isHeader(position: Int) = position < getHeaderCount() |
||||||
|
|
||||||
|
private fun isFooter(position: Int) = position >= getActualItemCount() + getHeaderCount() |
||||||
|
|
||||||
|
private fun getActualPosition(position: Int) = position - getHeaderCount() |
||||||
|
|
||||||
|
private fun addAnimation(holder: ItemViewHolder) { |
||||||
|
itemAnimation?.let { |
||||||
|
if (it.itemAnimEnabled) { |
||||||
|
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) { |
||||||
|
startAnimation(holder, it) |
||||||
|
it.itemAnimStartPosition = holder.layoutPosition |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) { |
||||||
|
item.itemAnimation?.let { |
||||||
|
for (anim in it.getAnimators(holder.itemView)) { |
||||||
|
anim.setDuration(item.itemAnimDuration).start() |
||||||
|
anim.interpolator = item.itemAnimInterpolator |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题, |
||||||
|
* 使用getItem(holder.layoutPosition)来获取item |
||||||
|
*/ |
||||||
|
abstract fun convert( |
||||||
|
holder: ItemViewHolder, |
||||||
|
binding: VB, |
||||||
|
item: ITEM, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) |
||||||
|
|
||||||
|
/** |
||||||
|
* 注册事件 |
||||||
|
*/ |
||||||
|
abstract fun registerListener(holder: ItemViewHolder, binding: VB) |
||||||
|
|
||||||
|
companion object { |
||||||
|
private const val TYPE_HEADER_VIEW = Int.MIN_VALUE |
||||||
|
private const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
|
||||||
|
class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : |
||||||
|
BaseAnimation { |
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> = |
||||||
|
arrayOf(ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)) |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
private const val DEFAULT_ALPHA_FROM = 0f |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
/** |
||||||
|
* adapter item 动画 |
||||||
|
*/ |
||||||
|
interface BaseAnimation { |
||||||
|
|
||||||
|
fun getAnimators(view: View): Array<Animator> |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
|
||||||
|
class ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) : |
||||||
|
BaseAnimation { |
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> { |
||||||
|
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", mFrom, 1f) |
||||||
|
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", mFrom, 1f) |
||||||
|
return arrayOf(scaleX, scaleY) |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
private const val DEFAULT_SCALE_FROM = .5f |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import xyz.fycz.myreader.base.adapter2.animations.BaseAnimation |
||||||
|
|
||||||
|
class SlideInBottomAnimation : BaseAnimation { |
||||||
|
|
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> = |
||||||
|
arrayOf(ObjectAnimator.ofFloat(view, "translationY", view.measuredHeight.toFloat(), 0f)) |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import xyz.fycz.myreader.base.adapter2.animations.BaseAnimation |
||||||
|
|
||||||
|
|
||||||
|
class SlideInLeftAnimation : BaseAnimation { |
||||||
|
|
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> = |
||||||
|
arrayOf(ObjectAnimator.ofFloat(view, "translationX", -view.rootView.width.toFloat(), 0f)) |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package xyz.fycz.myreader.base.adapter2.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import xyz.fycz.myreader.base.adapter2.animations.BaseAnimation |
||||||
|
|
||||||
|
|
||||||
|
class SlideInRightAnimation : BaseAnimation { |
||||||
|
|
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> = |
||||||
|
arrayOf(ObjectAnimator.ofFloat(view, "translationX", view.rootView.width.toFloat(), 0f)) |
||||||
|
} |
@ -0,0 +1,432 @@ |
|||||||
|
package xyz.fycz.myreader.ui.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import android.os.Handler |
||||||
|
import android.os.Looper |
||||||
|
import android.text.TextUtils |
||||||
|
import android.util.Log |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.DiffUtil |
||||||
|
import xyz.fycz.myreader.R |
||||||
|
import xyz.fycz.myreader.application.App |
||||||
|
import xyz.fycz.myreader.application.SysManager |
||||||
|
import xyz.fycz.myreader.base.BitIntentDataManager |
||||||
|
import xyz.fycz.myreader.base.adapter2.DiffRecyclerAdapter |
||||||
|
import xyz.fycz.myreader.base.adapter2.ItemViewHolder |
||||||
|
import xyz.fycz.myreader.base.adapter2.onClick |
||||||
|
import xyz.fycz.myreader.databinding.SearchBookItemBinding |
||||||
|
import xyz.fycz.myreader.entity.SearchBookBean |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book |
||||||
|
import xyz.fycz.myreader.model.SearchEngine |
||||||
|
import xyz.fycz.myreader.model.SearchEngine.OnGetBookInfoListener |
||||||
|
import xyz.fycz.myreader.model.mulvalmap.ConMVMap |
||||||
|
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager |
||||||
|
import xyz.fycz.myreader.ui.activity.BookDetailedActivity |
||||||
|
import xyz.fycz.myreader.util.help.StringHelper |
||||||
|
import xyz.fycz.myreader.util.utils.KeyWordUtils |
||||||
|
import xyz.fycz.myreader.util.utils.NetworkUtils |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils |
||||||
|
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil |
||||||
|
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/8/20 17:19 |
||||||
|
*/ |
||||||
|
|
||||||
|
class SearchAdapter( |
||||||
|
context: Context, |
||||||
|
val keyword: String, |
||||||
|
private val searchEngine: SearchEngine |
||||||
|
) : DiffRecyclerAdapter<SearchBookBean, SearchBookItemBinding>(context) { |
||||||
|
private val mBooks: ConMVMap<SearchBookBean, Book> = ConMVMap() |
||||||
|
private var mList: List<SearchBookBean> = ArrayList() |
||||||
|
private val tagList: MutableList<String> = ArrayList() |
||||||
|
private val handler = Handler(Looper.getMainLooper()) |
||||||
|
private var postTime = 0L |
||||||
|
private val sendRunnable = Runnable { upAdapter() } |
||||||
|
|
||||||
|
override val diffItemCallback: DiffUtil.ItemCallback<SearchBookBean> |
||||||
|
get() = object : DiffUtil.ItemCallback<SearchBookBean>() { |
||||||
|
override fun areItemsTheSame( |
||||||
|
oldItem: SearchBookBean, |
||||||
|
newItem: SearchBookBean |
||||||
|
): Boolean { |
||||||
|
return when { |
||||||
|
oldItem.name != newItem.name -> false |
||||||
|
oldItem.author != newItem.author -> false |
||||||
|
else -> true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun areContentsTheSame( |
||||||
|
oldItem: SearchBookBean, |
||||||
|
newItem: SearchBookBean |
||||||
|
): Boolean { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
override fun getChangePayload(oldItem: SearchBookBean, newItem: SearchBookBean): Any { |
||||||
|
val payload = Bundle() |
||||||
|
payload.putInt("sourceCount", newItem.sourceCount) |
||||||
|
if (oldItem.imgUrl != newItem.imgUrl) |
||||||
|
payload.putString("imgUrl", newItem.imgUrl) |
||||||
|
if (oldItem.type != newItem.type) |
||||||
|
payload.putString("type", newItem.type) |
||||||
|
if (oldItem.status != newItem.status) |
||||||
|
payload.putString("status", newItem.status) |
||||||
|
if (oldItem.wordCount != newItem.wordCount) |
||||||
|
payload.putString("wordCount", newItem.wordCount) |
||||||
|
if (oldItem.lastChapter != newItem.lastChapter) |
||||||
|
payload.putString("last", newItem.lastChapter) |
||||||
|
if (oldItem.desc != newItem.desc) |
||||||
|
payload.putString("desc", newItem.desc) |
||||||
|
return payload |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun getViewBinding(parent: ViewGroup): SearchBookItemBinding { |
||||||
|
return SearchBookItemBinding.inflate(inflater, parent, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun convert( |
||||||
|
holder: ItemViewHolder, |
||||||
|
binding: SearchBookItemBinding, |
||||||
|
item: SearchBookBean, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) { |
||||||
|
val payload = payloads.getOrNull(0) as? Bundle |
||||||
|
if (payload == null) { |
||||||
|
bind(binding, item) |
||||||
|
} else { |
||||||
|
val books = mBooks.getValues(item) |
||||||
|
books2SearchBookBean(item, books) |
||||||
|
bindChange(binding, item, payload) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun bind(binding: SearchBookItemBinding, data: SearchBookBean) { |
||||||
|
var aBooks = mBooks.getValues(data) |
||||||
|
if (aBooks == null || aBooks.size == 0) { |
||||||
|
aBooks = ArrayList() |
||||||
|
aBooks.add(searchBookBean2Book(data)) |
||||||
|
} |
||||||
|
val book = aBooks.getOrNull(0) ?: return |
||||||
|
val source = BookSourceManager.getBookSourceByStr(book.source) |
||||||
|
val rc = ReadCrawlerUtil.getReadCrawler(source) |
||||||
|
data.sourceName = source.sourceName |
||||||
|
books2SearchBookBean(data, aBooks) |
||||||
|
binding.run { |
||||||
|
if (data.imgUrl.isNullOrEmpty()) { |
||||||
|
data.imgUrl = "" |
||||||
|
} else { |
||||||
|
data.imgUrl = NetworkUtils.getAbsoluteURL(rc.nameSpace, data.imgUrl) |
||||||
|
} |
||||||
|
ivBookImg.load(data.imgUrl, data.name, data.author) |
||||||
|
KeyWordUtils.setKeyWord(tvBookName, data.name, keyword) |
||||||
|
if (data.author.isNullOrEmpty()) { |
||||||
|
data.author = "" |
||||||
|
} else { |
||||||
|
KeyWordUtils.setKeyWord(tvBookAuthor, data.author, keyword) |
||||||
|
} |
||||||
|
initTagList(this, data) |
||||||
|
if (data.lastChapter.isNullOrEmpty()) { |
||||||
|
data.lastChapter = "" |
||||||
|
} else { |
||||||
|
tvBookNewestChapter.text = context.getString( |
||||||
|
R.string.newest_chapter, |
||||||
|
data.lastChapter |
||||||
|
) |
||||||
|
} |
||||||
|
if (data.desc.isNullOrEmpty()) { |
||||||
|
data.desc = "" |
||||||
|
} else { |
||||||
|
tvBookDesc.text = String.format("简介:%s", data.desc) |
||||||
|
} |
||||||
|
tvBookSource.text = context.getString( |
||||||
|
R.string.source_title_num, |
||||||
|
data.sourceName, |
||||||
|
data.sourceCount |
||||||
|
) |
||||||
|
} |
||||||
|
App.getHandler().postDelayed({ |
||||||
|
val url = rc.nameSpace |
||||||
|
if (needGetInfo(data) && rc is BookInfoCrawler) { |
||||||
|
Log.i(data.name, "initOtherInfo") |
||||||
|
searchEngine.getBookInfo(book, rc) { isSuccess: Boolean -> |
||||||
|
if (isSuccess) { |
||||||
|
val books: MutableList<Book> = ArrayList() |
||||||
|
books.add(book) |
||||||
|
books2SearchBookBean(data, books) |
||||||
|
val payload = Bundle() |
||||||
|
if (!data.imgUrl.isNullOrEmpty()) |
||||||
|
payload.putString( |
||||||
|
"imgUrl", |
||||||
|
NetworkUtils.getAbsoluteURL(url, data.imgUrl) |
||||||
|
) |
||||||
|
if (!data.type.isNullOrEmpty()) |
||||||
|
payload.putString("type", data.type) |
||||||
|
if (!data.status.isNullOrEmpty()) |
||||||
|
payload.putString("status", data.status) |
||||||
|
if (!data.wordCount.isNullOrEmpty()) |
||||||
|
payload.putString("wordCount", data.wordCount) |
||||||
|
if (!data.lastChapter.isNullOrEmpty()) |
||||||
|
payload.putString("last", data.lastChapter) |
||||||
|
if (!data.desc.isNullOrEmpty()) |
||||||
|
payload.putString("desc", data.desc) |
||||||
|
bindChange(binding, data, payload) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, 1000) |
||||||
|
} |
||||||
|
|
||||||
|
private fun initTagList(binding: SearchBookItemBinding, data: SearchBookBean) { |
||||||
|
tagList.clear() |
||||||
|
val type = data.type |
||||||
|
if (!type.isNullOrEmpty()) tagList.add("0:$type") |
||||||
|
val wordCount = data.wordCount |
||||||
|
if (!wordCount.isNullOrEmpty()) tagList.add("1:$wordCount") |
||||||
|
val status = data.status |
||||||
|
if (!status.isNullOrEmpty()) tagList.add("2:$status") |
||||||
|
binding.run { |
||||||
|
if (tagList.size == 0) { |
||||||
|
tflBookTag.visibility = View.GONE |
||||||
|
} else { |
||||||
|
tflBookTag.visibility = View.VISIBLE |
||||||
|
tflBookTag.adapter = BookTagAdapter(context, tagList, 11) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun bindChange(binding: SearchBookItemBinding, data: SearchBookBean, payload: Bundle) { |
||||||
|
binding.run { |
||||||
|
initTagList(this, data) |
||||||
|
payload.keySet().forEach { |
||||||
|
when (it) { |
||||||
|
"sourceCount" -> tvBookSource.text = context.getString( |
||||||
|
R.string.source_title_num, |
||||||
|
data.sourceName, |
||||||
|
data.sourceCount |
||||||
|
) |
||||||
|
"imgUrl" -> ivBookImg.load( |
||||||
|
data.imgUrl, |
||||||
|
data.name, |
||||||
|
data.author |
||||||
|
) |
||||||
|
"last" -> tvBookNewestChapter.text = context.getString( |
||||||
|
R.string.newest_chapter, |
||||||
|
data.lastChapter |
||||||
|
) |
||||||
|
"desc" -> tvBookDesc.text = String.format("简介:%s", data.desc) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun registerListener(holder: ItemViewHolder, binding: SearchBookItemBinding) { |
||||||
|
binding.root.setOnClickListener { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
val books = mBooks.getValues(it) |
||||||
|
if (books == null || books.size == 0) return@let |
||||||
|
books[0] = searchBookBean2Book(it, books[0]) |
||||||
|
val intent = Intent( |
||||||
|
context, |
||||||
|
BookDetailedActivity::class.java |
||||||
|
) |
||||||
|
BitIntentDataManager.getInstance().putData(intent, books) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun needGetInfo(bookBean: SearchBookBean): Boolean { |
||||||
|
if (bookBean.author.isNullOrEmpty()) return true |
||||||
|
if (bookBean.type.isNullOrEmpty()) return true |
||||||
|
if (bookBean.desc.isNullOrEmpty()) return true |
||||||
|
return if (bookBean.lastChapter.isNullOrEmpty()) true else bookBean.imgUrl.isNullOrEmpty() |
||||||
|
} |
||||||
|
|
||||||
|
private fun books2SearchBookBean(bookBean: SearchBookBean, books: List<Book>) { |
||||||
|
bookBean.sourceCount = books.size |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.author)) break |
||||||
|
val author = book.author |
||||||
|
if (!StringHelper.isEmpty(author)) { |
||||||
|
bookBean.author = author |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.type)) break |
||||||
|
val type = book.type |
||||||
|
if (!StringHelper.isEmpty(type)) { |
||||||
|
bookBean.type = type |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.desc)) break |
||||||
|
val desc = book.desc |
||||||
|
if (!StringHelper.isEmpty(desc)) { |
||||||
|
bookBean.desc = desc |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.status)) break |
||||||
|
val status = book.status |
||||||
|
if (!StringHelper.isEmpty(status)) { |
||||||
|
bookBean.status = status |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.wordCount)) break |
||||||
|
val wordCount = book.wordCount |
||||||
|
if (!StringHelper.isEmpty(wordCount)) { |
||||||
|
bookBean.wordCount = wordCount |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.lastChapter)) break |
||||||
|
val lastChapter = book.newestChapterTitle |
||||||
|
if (!StringHelper.isEmpty(lastChapter)) { |
||||||
|
bookBean.lastChapter = lastChapter |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.updateTime)) break |
||||||
|
val updateTime = book.updateDate |
||||||
|
if (!StringHelper.isEmpty(updateTime)) { |
||||||
|
bookBean.updateTime = updateTime |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for (book in books) { |
||||||
|
if (!StringHelper.isEmpty(bookBean.imgUrl)) break |
||||||
|
val imgUrl = book.imgUrl |
||||||
|
if (!StringHelper.isEmpty(imgUrl)) { |
||||||
|
bookBean.imgUrl = imgUrl |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun searchBookBean2Book(bean: SearchBookBean, book: Book = Book()): Book { |
||||||
|
book.name = bean.name |
||||||
|
book.author = bean.author |
||||||
|
book.type = bean.type |
||||||
|
book.desc = bean.desc |
||||||
|
book.status = bean.status |
||||||
|
book.updateDate = bean.updateTime |
||||||
|
book.newestChapterTitle = bean.lastChapter |
||||||
|
book.wordCount = bean.wordCount |
||||||
|
return book |
||||||
|
} |
||||||
|
|
||||||
|
fun addAll(items: ConMVMap<SearchBookBean, Book>, searchKey: String) { |
||||||
|
mBooks.addAll(items) |
||||||
|
addAll(ArrayList(items.keySet()), searchKey) |
||||||
|
} |
||||||
|
|
||||||
|
fun addAll(newDataS: List<SearchBookBean>, keyWord: String?) { |
||||||
|
val copyDataS: MutableList<SearchBookBean> = ArrayList(getItems()) |
||||||
|
val filterDataS: MutableList<SearchBookBean> = ArrayList() |
||||||
|
when (SysManager.getSetting().searchFilter) { |
||||||
|
0 -> filterDataS.addAll(newDataS) |
||||||
|
1 -> for (ssb in newDataS) { |
||||||
|
if (StringUtils.isContainEachOther(ssb.name, keyWord) || |
||||||
|
StringUtils.isContainEachOther(ssb.author, keyWord) |
||||||
|
) { |
||||||
|
filterDataS.add(ssb) |
||||||
|
} |
||||||
|
} |
||||||
|
2 -> for (ssb in newDataS) { |
||||||
|
if (StringUtils.isEqual(ssb.name, keyWord) || |
||||||
|
StringUtils.isEqual(ssb.author, keyWord) |
||||||
|
) { |
||||||
|
filterDataS.add(ssb) |
||||||
|
} |
||||||
|
} |
||||||
|
else -> for (ssb in newDataS) { |
||||||
|
if (StringUtils.isContainEachOther(ssb.name, keyWord) || |
||||||
|
StringUtils.isContainEachOther(ssb.author, keyWord) |
||||||
|
) { |
||||||
|
filterDataS.add(ssb) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (filterDataS.size > 0) { |
||||||
|
val searchBookBeansAdd: MutableList<SearchBookBean> = java.util.ArrayList() |
||||||
|
if (copyDataS.size == 0) { |
||||||
|
copyDataS.addAll(filterDataS) |
||||||
|
} else { |
||||||
|
//存在 |
||||||
|
for (temp in filterDataS) { |
||||||
|
var hasSame = false |
||||||
|
var i = 0 |
||||||
|
val size = copyDataS.size |
||||||
|
while (i < size) { |
||||||
|
val searchBook = copyDataS[i] |
||||||
|
if (TextUtils.equals(temp.name, searchBook.name) |
||||||
|
&& TextUtils.equals(temp.author, searchBook.author) |
||||||
|
) { |
||||||
|
hasSame = true |
||||||
|
break |
||||||
|
} |
||||||
|
i++ |
||||||
|
} |
||||||
|
if (!hasSame) { |
||||||
|
searchBookBeansAdd.add(temp) |
||||||
|
} |
||||||
|
} |
||||||
|
//添加 |
||||||
|
for (temp in searchBookBeansAdd) { |
||||||
|
if (TextUtils.equals(keyWord, temp.name)) { |
||||||
|
for (i in copyDataS.indices) { |
||||||
|
val searchBook = copyDataS[i] |
||||||
|
if (!TextUtils.equals(keyWord, searchBook.name)) { |
||||||
|
copyDataS.add(i, temp) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (TextUtils.equals(keyWord, temp.author)) { |
||||||
|
for (i in copyDataS.indices) { |
||||||
|
val searchBook = copyDataS[i] |
||||||
|
if (!TextUtils.equals(keyWord, searchBook.name) && !TextUtils.equals( |
||||||
|
keyWord, |
||||||
|
searchBook.author |
||||||
|
) |
||||||
|
) { |
||||||
|
copyDataS.add(i, temp) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
copyDataS.add(temp) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
mList = copyDataS |
||||||
|
upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Synchronized |
||||||
|
private fun upAdapter() { |
||||||
|
if (System.currentTimeMillis() >= postTime + 500) { |
||||||
|
handler.removeCallbacks(sendRunnable) |
||||||
|
postTime = System.currentTimeMillis() |
||||||
|
setItems(mList) |
||||||
|
} else { |
||||||
|
handler.removeCallbacks(sendRunnable) |
||||||
|
handler.postDelayed(sendRunnable, 500 - System.currentTimeMillis() + postTime) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue