commit
31d0b722e2
@ -0,0 +1,398 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.SparseArray |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.GridLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/11/24. |
||||||
|
* |
||||||
|
* 通用的adapter 可添加header,footer,以及不同类型item |
||||||
|
*/ |
||||||
|
abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : RecyclerView.Adapter<ItemViewHolder>() { |
||||||
|
|
||||||
|
constructor(context: Context, vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>) : this(context) { |
||||||
|
addItemViewDelegates(*delegates) |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, vararg delegates: ItemViewDelegate<ITEM>) : this(context) { |
||||||
|
addItemViewDelegates(*delegates) |
||||||
|
} |
||||||
|
|
||||||
|
private val inflater: LayoutInflater = LayoutInflater.from(context) |
||||||
|
|
||||||
|
private var headerItems: SparseArray<View>? = null |
||||||
|
private var footerItems: SparseArray<View>? = null |
||||||
|
|
||||||
|
private val itemDelegates: HashMap<Int, ItemViewDelegate<ITEM>> = hashMapOf() |
||||||
|
private val items: MutableList<ITEM> = mutableListOf() |
||||||
|
|
||||||
|
private val lock = Object() |
||||||
|
|
||||||
|
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null |
||||||
|
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null |
||||||
|
|
||||||
|
private 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 |
||||||
|
} |
||||||
|
|
||||||
|
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegate(viewType: Int, delegate: DELEGATE) { |
||||||
|
itemDelegates.put(viewType, delegate) |
||||||
|
} |
||||||
|
|
||||||
|
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegate(delegate: DELEGATE) { |
||||||
|
itemDelegates.put(itemDelegates.size, delegate) |
||||||
|
} |
||||||
|
|
||||||
|
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegates(vararg delegates: DELEGATE) { |
||||||
|
delegates.forEach { |
||||||
|
addItemViewDelegate(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addItemViewDelegates(vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>) { |
||||||
|
delegates.forEach { |
||||||
|
addItemViewDelegate(it.first, it.second) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addHeaderView(header: View) { |
||||||
|
synchronized(lock) { |
||||||
|
if (headerItems == null) { |
||||||
|
headerItems = SparseArray() |
||||||
|
} |
||||||
|
headerItems?.let { |
||||||
|
val index = it.size() |
||||||
|
it.put(TYPE_HEADER_VIEW + it.size(), header) |
||||||
|
notifyItemInserted(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addFooterView(footer: View) { |
||||||
|
synchronized(lock) { |
||||||
|
if (footerItems == null) { |
||||||
|
footerItems = SparseArray() |
||||||
|
} |
||||||
|
footerItems?.let { |
||||||
|
val index = getActualItemCount() + it.size() |
||||||
|
it.put(TYPE_FOOTER_VIEW + it.size(), footer) |
||||||
|
notifyItemInserted(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun removeHeaderView(header: View) { |
||||||
|
synchronized(lock) { |
||||||
|
headerItems?.let { |
||||||
|
val index = it.indexOfValue(header) |
||||||
|
if (index >= 0) { |
||||||
|
it.remove(index) |
||||||
|
notifyItemRemoved(index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun removeFooterView(footer: View) { |
||||||
|
synchronized(lock) { |
||||||
|
footerItems?.let { |
||||||
|
val index = it.indexOfValue(footer) |
||||||
|
if (index >= 0) { |
||||||
|
it.remove(index) |
||||||
|
notifyItemRemoved(getActualItemCount() + index - 2) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setItems(items: List<ITEM>?) { |
||||||
|
synchronized(lock) { |
||||||
|
if (this.items.isNotEmpty()) { |
||||||
|
this.items.clear() |
||||||
|
} |
||||||
|
if (items != null) { |
||||||
|
this.items.addAll(items) |
||||||
|
} |
||||||
|
notifyDataSetChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setItem(position: Int, item: ITEM) { |
||||||
|
synchronized(lock) { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (position in 0 until oldSize) { |
||||||
|
this.items[position] = item |
||||||
|
notifyItemChanged(position + getHeaderCount()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addItem(item: ITEM) { |
||||||
|
synchronized(lock) { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (this.items.add(item)) { |
||||||
|
if (oldSize == 0) { |
||||||
|
notifyDataSetChanged() |
||||||
|
} else { |
||||||
|
notifyItemInserted(oldSize + getHeaderCount()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addItems(position: Int, newItems: List<ITEM>) { |
||||||
|
synchronized(lock) { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (position in 0 until oldSize) { |
||||||
|
if (if (oldSize == 0) this.items.addAll(newItems) else this.items.addAll(position, newItems)) { |
||||||
|
if (oldSize == 0) { |
||||||
|
notifyDataSetChanged() |
||||||
|
} else { |
||||||
|
notifyItemRangeChanged(position + getHeaderCount(), newItems.size) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addItems(newItems: List<ITEM>) { |
||||||
|
synchronized(lock) { |
||||||
|
val oldSize = getActualItemCount() |
||||||
|
if (this.items.addAll(newItems)) { |
||||||
|
if (oldSize == 0) { |
||||||
|
notifyDataSetChanged() |
||||||
|
} else { |
||||||
|
notifyItemRangeChanged(oldSize + getHeaderCount(), newItems.size) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun removeItem(position: Int) { |
||||||
|
synchronized(lock) { |
||||||
|
if (this.items.removeAt(position) != null) |
||||||
|
notifyItemRemoved(position + getHeaderCount()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun removeItem(item: ITEM) { |
||||||
|
synchronized(lock) { |
||||||
|
if (this.items.remove(item)) |
||||||
|
notifyItemRemoved(this.items.indexOf(item) + getHeaderCount()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun removeItems(items: List<ITEM>) { |
||||||
|
synchronized(lock) { |
||||||
|
if (this.items.removeAll(items)) |
||||||
|
notifyDataSetChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun swapItem(oldPosition: Int, newPosition: Int) { |
||||||
|
synchronized(lock) { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (oldPosition in 0 until size && newPosition in 0 until size) { |
||||||
|
Collections.swap(this.items, oldPosition + getHeaderCount(), newPosition + getHeaderCount()) |
||||||
|
notifyDataSetChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun updateItem(position: Int, payload: Any) { |
||||||
|
synchronized(lock) { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (position in 0 until size) { |
||||||
|
notifyItemChanged(position + getHeaderCount(), payload) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) { |
||||||
|
synchronized(lock) { |
||||||
|
val size = getActualItemCount() |
||||||
|
if (fromPosition in 0 until size && toPosition in 0 until size) { |
||||||
|
notifyItemRangeChanged(fromPosition + getHeaderCount(), toPosition - fromPosition + 1, payloads) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun isEmpty(): Boolean { |
||||||
|
return items.isEmpty() |
||||||
|
} |
||||||
|
|
||||||
|
fun isNotEmpty(): Boolean { |
||||||
|
return items.isNotEmpty() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 除去header和footer |
||||||
|
*/ |
||||||
|
fun getActualItemCount(): Int { |
||||||
|
return items.size |
||||||
|
} |
||||||
|
|
||||||
|
fun getHeaderCount(): Int { |
||||||
|
return headerItems?.size() ?: 0 |
||||||
|
} |
||||||
|
|
||||||
|
fun getFooterCount(): Int { |
||||||
|
return footerItems?.size() ?: 0 |
||||||
|
} |
||||||
|
|
||||||
|
fun getItem(position: Int): ITEM = items[position % items.size] |
||||||
|
|
||||||
|
fun getItems(): List<ITEM> = items |
||||||
|
|
||||||
|
protected open fun getItemViewType(item: ITEM, position: Int): Int { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* grid 模式下使用 |
||||||
|
*/ |
||||||
|
protected open fun getSpanSize(item: ITEM, viewType: Int, position: Int): Int { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
final override fun getItemCount(): Int { |
||||||
|
return getActualItemCount() + getHeaderCount() + getFooterCount() |
||||||
|
} |
||||||
|
|
||||||
|
final override fun getItemViewType(position: Int): Int { |
||||||
|
return when { |
||||||
|
isHeader(position) -> TYPE_HEADER_VIEW + position |
||||||
|
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount() |
||||||
|
else -> getItemViewType(getItem(getRealPosition(position)), getRealPosition(position)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { |
||||||
|
return when { |
||||||
|
viewType < TYPE_HEADER_VIEW + getHeaderCount() -> { |
||||||
|
ItemViewHolder(headerItems!!.get(viewType)) |
||||||
|
} |
||||||
|
|
||||||
|
viewType >= TYPE_FOOTER_VIEW -> { |
||||||
|
ItemViewHolder(footerItems!!.get(viewType)) |
||||||
|
} |
||||||
|
|
||||||
|
else -> { |
||||||
|
val holder = ItemViewHolder(inflater.inflate(itemDelegates.getValue(viewType).layoutID, parent, false)) |
||||||
|
|
||||||
|
if (itemClickListener != null) { |
||||||
|
holder.itemView.setOnClickListener { |
||||||
|
itemClickListener!!.invoke(holder, getItem(holder.layoutPosition)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (itemLongClickListener != null) { |
||||||
|
holder.itemView.setOnLongClickListener { |
||||||
|
itemLongClickListener!!.invoke(holder, getItem(holder.layoutPosition)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
holder |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { |
||||||
|
onBindViewHolder(holder, position, mutableListOf()) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) { |
||||||
|
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) { |
||||||
|
itemDelegates.getValue(getItemViewType(holder.layoutPosition)) |
||||||
|
.convert(holder, getItem(holder.layoutPosition), 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 if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize( |
||||||
|
getItem(position), getItemViewType(position), position |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setAnimationConfig(item: ItemAnimation) { |
||||||
|
itemAnimation = item |
||||||
|
} |
||||||
|
|
||||||
|
private fun isHeader(position: Int): Boolean { |
||||||
|
return position < getHeaderCount() |
||||||
|
} |
||||||
|
|
||||||
|
private fun isFooter(position: Int): Boolean { |
||||||
|
return position >= getActualItemCount() + getHeaderCount() |
||||||
|
} |
||||||
|
|
||||||
|
private fun getRealPosition(position: Int): Int { |
||||||
|
return position - getHeaderCount() |
||||||
|
} |
||||||
|
|
||||||
|
private fun addAnimation(holder: ItemViewHolder) { |
||||||
|
if (itemAnimation == null) { |
||||||
|
itemAnimation = ItemAnimation.create().enabled(true) |
||||||
|
} |
||||||
|
|
||||||
|
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) { |
||||||
|
for (anim in item.itemAnimation.getAnimators(holder.itemView)) { |
||||||
|
anim.setDuration(item.itemAnimDuration).start() |
||||||
|
anim.interpolator = item.itemAnimInterpolator |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
private const val TYPE_HEADER_VIEW = Int.MIN_VALUE |
||||||
|
private const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/12/15. |
||||||
|
* |
||||||
|
* 上拉加载更多 |
||||||
|
*/ |
||||||
|
abstract class InfiniteScrollListener() : RecyclerView.OnScrollListener() { |
||||||
|
private val loadMoreRunnable = Runnable { onLoadMore() } |
||||||
|
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { |
||||||
|
// if (dy < 0 || dataLoading.isDataLoading()) return |
||||||
|
|
||||||
|
val layoutManager:LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager |
||||||
|
val visibleItemCount = recyclerView.childCount |
||||||
|
val totalItemCount = layoutManager.itemCount |
||||||
|
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition() |
||||||
|
|
||||||
|
if (totalItemCount - visibleItemCount <= firstVisibleItem + VISIBLE_THRESHOLD) { |
||||||
|
recyclerView.post(loadMoreRunnable) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract fun onLoadMore() |
||||||
|
|
||||||
|
companion object { |
||||||
|
private const val VISIBLE_THRESHOLD = 5 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import android.view.animation.Interpolator |
||||||
|
import android.view.animation.LinearInterpolator |
||||||
|
import io.legado.app.base.adapter.animations.AlphaInAnimation |
||||||
|
import io.legado.app.base.adapter.animations.BaseAnimation |
||||||
|
import io.legado.app.base.adapter.animations.ScaleInAnimation |
||||||
|
import io.legado.app.base.adapter.animations.SlideInBottomAnimation |
||||||
|
import io.legado.app.base.adapter.animations.SlideInLeftAnimation |
||||||
|
import io.legado.app.base.adapter.animations.SlideInRightAnimation |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/12/15. |
||||||
|
*/ |
||||||
|
class ItemAnimation private constructor() { |
||||||
|
|
||||||
|
var itemAnimEnabled = false |
||||||
|
var itemAnimFirstOnly = true |
||||||
|
var itemAnimation: BaseAnimation = SlideInBottomAnimation() |
||||||
|
var itemAnimInterpolator: Interpolator = LinearInterpolator() |
||||||
|
var itemAnimDuration: Long = 300L |
||||||
|
var itemAnimStartPosition: Int = -1 |
||||||
|
|
||||||
|
fun interpolator(interpolator: Interpolator): ItemAnimation { |
||||||
|
itemAnimInterpolator = interpolator |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun duration(duration: Long): ItemAnimation { |
||||||
|
itemAnimDuration = duration |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun startPostion(startPos: Int): ItemAnimation { |
||||||
|
itemAnimStartPosition = startPos |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun animation(animationType: Int = FADE_IN, animation: BaseAnimation? = null): ItemAnimation { |
||||||
|
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() |
||||||
|
} |
||||||
|
} |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun enabled(enabled: Boolean): ItemAnimation { |
||||||
|
itemAnimEnabled = enabled |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun firstOnly(firstOnly: Boolean): ItemAnimation { |
||||||
|
itemAnimFirstOnly = firstOnly |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 { |
||||||
|
return ItemAnimation() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/11/24. |
||||||
|
* |
||||||
|
* item代理, |
||||||
|
*/ |
||||||
|
abstract class ItemViewDelegate<in ITEM>(protected val context: Context) { |
||||||
|
|
||||||
|
abstract val layoutID: Int |
||||||
|
|
||||||
|
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>) |
||||||
|
|
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import android.view.View |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/11/28. |
||||||
|
*/ |
||||||
|
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) |
@ -0,0 +1,26 @@ |
|||||||
|
package io.legado.app.base.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by Invincible on 2017/12/15. |
||||||
|
*/ |
||||||
|
abstract class SimpleRecyclerAdapter<ITEM>(context: Context) : CommonRecyclerAdapter<ITEM>(context) { |
||||||
|
|
||||||
|
init { |
||||||
|
addItemViewDelegate(object : ItemViewDelegate<ITEM>(context) { |
||||||
|
override val layoutID: Int |
||||||
|
get() = this@SimpleRecyclerAdapter.layoutID |
||||||
|
|
||||||
|
override fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>) { |
||||||
|
this@SimpleRecyclerAdapter.convert(holder, item, payloads) |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
abstract val layoutID: Int |
||||||
|
|
||||||
|
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>) |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
package io.legado.app.base.adapter.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 io.legado.app.base.adapter.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
/** |
||||||
|
* adapter item 动画 |
||||||
|
*/ |
||||||
|
interface BaseAnimation { |
||||||
|
|
||||||
|
fun getAnimators(view: View): Array<Animator> |
||||||
|
|
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package io.legado.app.base.adapter.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 io.legado.app.base.adapter.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import io.legado.app.base.adapter.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 io.legado.app.base.adapter.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import io.legado.app.base.adapter.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 io.legado.app.base.adapter.animations |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.view.View |
||||||
|
import io.legado.app.base.adapter.animations.BaseAnimation |
||||||
|
|
||||||
|
|
||||||
|
class SlideInRightAnimation : BaseAnimation { |
||||||
|
|
||||||
|
|
||||||
|
override fun getAnimators(view: View): Array<Animator> = |
||||||
|
arrayOf(ObjectAnimator.ofFloat(view, "translationX", view.rootView.width.toFloat(), 0f)) |
||||||
|
} |
@ -1,2 +1,3 @@ |
|||||||
package io.legado.app.data.entities |
package io.legado.app.data.entities |
||||||
|
|
||||||
|
class SearchBook |
@ -1,5 +0,0 @@ |
|||||||
package io.legado.app.ui.main |
|
||||||
|
|
||||||
class MainModel { |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,34 @@ |
|||||||
|
package io.legado.app.ui.search |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.adapter.ItemViewDelegate |
||||||
|
import io.legado.app.base.adapter.ItemViewHolder |
||||||
|
import io.legado.app.base.adapter.SimpleRecyclerAdapter |
||||||
|
import io.legado.app.data.entities.SearchBook |
||||||
|
import kotlinx.android.synthetic.main.item_search.view.* |
||||||
|
|
||||||
|
class SearchAdapter(context: Context) : SimpleRecyclerAdapter<SearchBook>(context) { |
||||||
|
|
||||||
|
init { |
||||||
|
addItemViewDelegate(TestItemDelegate(context)) |
||||||
|
} |
||||||
|
|
||||||
|
override val layoutID: Int |
||||||
|
get() = R.layout.item_search |
||||||
|
|
||||||
|
override fun convert(holder: ItemViewHolder, item: SearchBook, payloads: MutableList<Any>) { |
||||||
|
holder.itemView.bookName.text = "我欲封天" |
||||||
|
} |
||||||
|
|
||||||
|
internal class TestItemDelegate(context: Context) : ItemViewDelegate<SearchBook>(context){ |
||||||
|
override val layoutID: Int |
||||||
|
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. |
||||||
|
|
||||||
|
override fun convert(holder: ItemViewHolder, item: SearchBook, payloads: MutableList<Any>) { |
||||||
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
package io.legado.app.utils |
||||||
|
|
||||||
|
fun String?.strim() = if (this.isNullOrBlank()) null else this.trim() |
||||||
|
|
||||||
|
fun String.isAbsUrl() = this.startsWith("http://", true) |
||||||
|
|| this.startsWith("https://", true) |
@ -1,26 +1,29 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<androidx.drawerlayout.widget.DrawerLayout |
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
xmlns:android="http://schemas.android.com/apk/res/android" |
xmlns:tools="http://schemas.android.com/tools"> |
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
<data class=".ui.main.MainDataBinding"> |
||||||
xmlns:tools="http://schemas.android.com/tools" |
<variable name="MainViewModel" type="io.legado.app.ui.main.MainViewModel"/> |
||||||
android:id="@+id/drawer_layout" |
</data> |
||||||
android:layout_width="match_parent" |
<androidx.drawerlayout.widget.DrawerLayout |
||||||
android:layout_height="match_parent" |
android:id="@+id/drawer_layout" |
||||||
android:fitsSystemWindows="true" |
|
||||||
tools:openDrawer="start"> |
|
||||||
|
|
||||||
<include |
|
||||||
layout="@layout/app_bar_main" |
|
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="match_parent"/> |
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView |
|
||||||
android:id="@+id/nav_view" |
|
||||||
android:layout_width="wrap_content" |
|
||||||
android:layout_height="match_parent" |
android:layout_height="match_parent" |
||||||
android:layout_gravity="start" |
|
||||||
android:fitsSystemWindows="true" |
android:fitsSystemWindows="true" |
||||||
app:headerLayout="@layout/nav_header_main" |
tools:openDrawer="start"> |
||||||
app:menu="@menu/activity_main_drawer"/> |
|
||||||
|
<include |
||||||
|
layout="@layout/app_bar_main" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"/> |
||||||
|
|
||||||
|
<com.google.android.material.navigation.NavigationView |
||||||
|
android:id="@+id/nav_view" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="start" |
||||||
|
android:fitsSystemWindows="true" |
||||||
|
app:headerLayout="@layout/nav_header_main" |
||||||
|
app:menu="@menu/activity_main_drawer"/> |
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout> |
</androidx.drawerlayout.widget.DrawerLayout> |
||||||
|
</layout> |
||||||
|
@ -0,0 +1,19 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content"> |
||||||
|
|
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/bookName" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
android:text="测试"/> |
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
Loading…
Reference in new issue