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 |
||||
|
||||
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"?> |
||||
<androidx.drawerlayout.widget.DrawerLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:id="@+id/drawer_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:fitsSystemWindows="true" |
||||
tools:openDrawer="start"> |
||||
|
||||
<include |
||||
layout="@layout/app_bar_main" |
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tools="http://schemas.android.com/tools"> |
||||
<data class=".ui.main.MainDataBinding"> |
||||
<variable name="MainViewModel" type="io.legado.app.ui.main.MainViewModel"/> |
||||
</data> |
||||
<androidx.drawerlayout.widget.DrawerLayout |
||||
android:id="@+id/drawer_layout" |
||||
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"/> |
||||
tools:openDrawer="start"> |
||||
|
||||
<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