优化搜索

master
fengyuecanzhu 3 years ago
parent 378d2e56d1
commit 08d0a46204
  1. 1
      .idea/misc.xml
  2. 28
      app/src/main/java/xyz/fycz/myreader/base/adapter2/Click.kt
  3. 215
      app/src/main/java/xyz/fycz/myreader/base/adapter2/DiffRecyclerAdapter.kt
  4. 80
      app/src/main/java/xyz/fycz/myreader/base/adapter2/ItemAnimation.kt
  5. 10
      app/src/main/java/xyz/fycz/myreader/base/adapter2/ItemViewHolder.kt
  6. 452
      app/src/main/java/xyz/fycz/myreader/base/adapter2/RecyclerAdapter.kt
  7. 18
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/AlphaInAnimation.kt
  8. 13
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/BaseAnimation.kt
  9. 21
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/ScaleInAnimation.kt
  10. 13
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/SlideInBottomAnimation.kt
  11. 14
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/SlideInLeftAnimation.kt
  12. 14
      app/src/main/java/xyz/fycz/myreader/base/adapter2/animations/SlideInRightAnimation.kt
  13. 18
      app/src/main/java/xyz/fycz/myreader/entity/SearchBookBean.java
  14. 24
      app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
  15. 432
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchAdapter.kt
  16. 3
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java
  17. 4
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java
  18. 5
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
  19. 0
      app/src/main/res/layout/search_book_item.xml

@ -4,6 +4,7 @@
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.264" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/listview_search_book_item.xml" value="0.22010869565217392" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/view_file_picker.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book.xml" value="0.13703703703703704" />
</map>

@ -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 可添加headerfooter以及不同类型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))
}

@ -16,6 +16,8 @@ public class SearchBookBean {
private String lastChapter;
private String updateTime;
private String imgUrl;
private String sourceName;
private int sourceCount;
public SearchBookBean() {
}
@ -97,6 +99,22 @@ public class SearchBookBean {
this.imgUrl = imgUrl;
}
public String getSourceName() {
return sourceName;
}
public void setSourceName(String sourceName) {
this.sourceName = sourceName;
}
public int getSourceCount() {
return sourceCount;
}
public void setSourceCount(int sourceCount) {
this.sourceCount = sourceCount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

@ -53,6 +53,7 @@ import xyz.fycz.myreader.greendao.service.SearchHistoryService;
import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.model.mulvalmap.ConMVMap;
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager;
import xyz.fycz.myreader.ui.adapter.SearchAdapter;
import xyz.fycz.myreader.ui.adapter.SearchBookAdapter;
import xyz.fycz.myreader.ui.adapter.SearchHistoryAdapter;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -73,10 +74,8 @@ public class SearchBookActivity extends BaseActivity {
private ActivitySearchBookBinding binding;
private SearchBookAdapter mSearchBookAdapter;
private SearchAdapter mSearchBookAdapter;
private String searchKey;//搜索关键字
private List<SearchBookBean> mBooksBean = new ArrayList<>();
private ConMVMap<SearchBookBean, Book> mBooks = new ConMVMap<>();
private List<SearchHistory> mSearchHistories = new ArrayList<>();
private List<CharSequence> mSuggestions = new ArrayList<>();
private List<CharSequence> mHotKeys = new ArrayList<>();
@ -134,8 +133,7 @@ public class SearchBookActivity extends BaseActivity {
@Override
public void loadMoreSearchBook(ConMVMap<SearchBookBean, Book> items) {
mBooks.addAll(items);
mSearchBookAdapter.addAll(new ArrayList<>(items.keySet()), searchKey);
mSearchBookAdapter.addAll(items, searchKey);
binding.srlSearchBookList.finishRefresh();
}
@ -372,6 +370,7 @@ public class SearchBookActivity extends BaseActivity {
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean) {
@ -416,6 +415,7 @@ public class SearchBookActivity extends BaseActivity {
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NotNull Boolean b) {
initSuggestionList();
@ -552,8 +552,6 @@ public class SearchBookActivity extends BaseActivity {
*/
private void getData() {
initSearchList();
mBooksBean.clear();
mBooks.clear();
List<ReadCrawler> readCrawlers = ReadCrawlerUtil
.getEnableReadCrawlers(SharedPreUtils.getInstance().getString("searchGroup"));
allThreadCount = readCrawlers.size();
@ -586,18 +584,8 @@ public class SearchBookActivity extends BaseActivity {
binding.srlSearchBookList.setEnableRefresh(false);
} else {
showBooks = true;
mSearchBookAdapter = new SearchBookAdapter(this, mBooks, searchEngine, searchKey);
mSearchBookAdapter = new SearchAdapter(this, searchKey, searchEngine);
binding.rvSearchBooksList.setAdapter(mSearchBookAdapter);
//进入书籍详情页
mSearchBookAdapter.setOnItemClickListener((view, pos) -> {
SearchBookBean data = mSearchBookAdapter.getItem(pos);
ArrayList<Book> books = (ArrayList<Book>) mBooks.getValues(data);
if (books == null || books.size() == 0) return;
searchBookBean2Book(data, books.get(0));
Intent intent = new Intent(this, BookDetailedActivity.class);
BitIntentDataManager.getInstance().putData(intent, books);
startActivity(intent);
});
binding.srlSearchBookList.setEnableRefresh(true);
binding.rvSearchBooksList.setVisibility(View.VISIBLE);
binding.llSuggestBooksView.setVisibility(View.GONE);

@ -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)
}
}
}

@ -5,7 +5,6 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
@ -42,7 +41,7 @@ public class SearchBookAdapter extends BaseListAdapter<SearchBookBean> {
@Override
protected IViewHolder<SearchBookBean> createViewHolder(int viewType) {
return new SearchBookHolder(activity, mBooks, searchEngine, keyWord, this);
return new SearchBookHolder(activity, mBooks, searchEngine, keyWord);
}
public void addAll(List<SearchBookBean> newDataS, String keyWord) {

@ -18,13 +18,11 @@ import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager;
import xyz.fycz.myreader.ui.adapter.BookTagAdapter;
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.RxUtils;
import xyz.fycz.myreader.webapi.BookApi;
@ -50,7 +48,7 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
@Override
protected int getItemLayoutId() {
return R.layout.listview_search_book_item;
return R.layout.search_book_item;
}
@Override

@ -23,7 +23,6 @@ import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.model.mulvalmap.ConMVMap;
import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager;
import xyz.fycz.myreader.ui.adapter.BookTagAdapter;
import xyz.fycz.myreader.ui.adapter.SearchBookAdapter;
import xyz.fycz.myreader.util.help.StringHelper;
import xyz.fycz.myreader.util.utils.KeyWordUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils;
@ -43,7 +42,7 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
private String keyWord;
private List<String> tagList = new ArrayList<>();
public SearchBookHolder(Activity activity, ConMVMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine, String keyWord, SearchBookAdapter adapter) {
public SearchBookHolder(Activity activity, ConMVMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine, String keyWord) {
this.activity = activity;
this.mBooks = mBooks;
this.searchEngine = searchEngine;
@ -61,7 +60,7 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
@Override
protected int getItemLayoutId() {
return R.layout.listview_search_book_item;
return R.layout.search_book_item;
}
@Override

Loading…
Cancel
Save