SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ItemListAdapter.kt

246 lines
9.0 KiB
Kotlin

package jp.juggler.subwaytooter.columnviewholder
import android.annotation.SuppressLint
import android.os.Handler
import android.os.SystemClock
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.column.Column
import jp.juggler.subwaytooter.column.HeaderType
import jp.juggler.subwaytooter.column.toAdapterIndex
import jp.juggler.subwaytooter.column.toListIndex
import jp.juggler.subwaytooter.itemviewholder.ItemViewHolder
import jp.juggler.subwaytooter.itemviewholder.bind
import jp.juggler.util.log.LogCategory
import jp.juggler.util.ui.AdapterChange
import jp.juggler.util.ui.AdapterChangeType
class ItemListAdapter(
private val activity: ActMain,
private val column: Column,
internal val columnVh: ColumnViewHolder,
private val bSimpleList: Boolean,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private val log = LogCategory("ItemListAdapter")
}
private class DiffCallback(
val oldList: List<TimelineItem>,
val newList: List<TimelineItem>,
val biasListIndex: Int,
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size - biasListIndex
}
override fun getNewListSize(): Int {
return newList.size - biasListIndex
}
override fun areItemsTheSame(oldAdapterIndex: Int, newAdapterIndex: Int): Boolean {
val oldListIndex = oldAdapterIndex + biasListIndex
val newListIndex = newAdapterIndex + biasListIndex
// header?
if (oldListIndex < 0 || newListIndex < 0) return oldListIndex == newListIndex
// compare object address
return oldList[oldListIndex] === newList[newListIndex]
}
override fun areContentsTheSame(oldAdapterIndex: Int, newAdapterIndex: Int): Boolean {
val oldListIndex = oldAdapterIndex + biasListIndex
val newListIndex = newAdapterIndex + biasListIndex
// headerは毎回更新する
return !(oldListIndex < 0 || newListIndex < 0)
}
}
private var list: ArrayList<TimelineItem>
private val handler: Handler
init {
this.list = ArrayList()
this.handler = activity.handler
setHasStableIds(true)
}
override fun getItemCount(): Int {
return column.toAdapterIndex(column.listData.size)
}
override fun getItemId(position: Int): Long {
return try {
list[column.toListIndex(position)].listViewItemId
} catch (ignored: Throwable) {
0L
}
}
override fun getItemViewType(position: Int): Int {
val headerType = column.type.headerType
if (headerType == null || position > 0) return 0
return headerType.viewType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
activity
when (viewType) {
0 -> {
val holder = ItemViewHolder(activity)
holder.viewRoot.tag = holder
return ViewHolderItem(holder)
}
HeaderType.Profile.viewType -> {
return ViewHolderHeaderProfile(activity, parent)
}
HeaderType.Search.viewType -> {
return ViewHolderHeaderSearch(activity, parent)
}
HeaderType.Instance.viewType -> {
return ViewHolderHeaderInstance(activity, parent)
}
HeaderType.Filter.viewType -> {
return ViewHolderHeaderFilter(activity, parent)
}
HeaderType.ProfileDirectory.viewType -> {
return ViewHolderHeaderProfileDirectory(activity, parent)
}
else -> error("unknown viewType: $viewType")
}
}
fun findHeaderViewHolder(listView: RecyclerView): ViewHolderHeaderBase? {
return when (column.type.headerType) {
null -> null
else -> listView.findViewHolderForAdapterPosition(0) as? ViewHolderHeaderBase
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, adapterIndex: Int) {
if (holder is ViewHolderItem) {
val listIndex = column.toListIndex(adapterIndex)
holder.ivh.bind(this, column, bSimpleList, list[listIndex])
} else if (holder is ViewHolderHeaderBase) {
holder.bindData(column)
}
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is ViewHolderItem) {
holder.ivh.onViewRecycled()
} else if (holder is ViewHolderHeaderBase) {
holder.onViewRecycled()
}
}
@SuppressLint("NotifyDataSetChanged")
fun notifyChange(
reason: String,
changeList: List<AdapterChange>? = null,
reset: Boolean = false,
) {
val timeStart = SystemClock.elapsedRealtime()
// カラムから最新データをコピーする
val newList = ArrayList<TimelineItem>()
newList.ensureCapacity(column.listData.size)
newList.addAll(column.listData)
when {
// 変更リストが指定された場合はヘッダ部分と変更部分を通知する
changeList != null -> {
log.d("notifyChange: changeList=${changeList.size},reason=$reason")
this.list = newList
// ヘッダは毎回更新する
// (ヘッダだけ更新するためにカラのchangeListが渡される)
if (column.type.headerType != null) {
notifyItemRangeChanged(0, 1)
}
// 変更リストを順番に通知する
for (c in changeList) {
val adapterIndex = column.toAdapterIndex(c.listIndex)
log.d("notifyChange: ChangeType=${c.type} offset=$adapterIndex,count=${c.count}")
when (c.type) {
AdapterChangeType.RangeInsert -> notifyItemRangeInserted(
adapterIndex,
c.count
)
AdapterChangeType.RangeRemove -> notifyItemRangeRemoved(
adapterIndex,
c.count
)
AdapterChangeType.RangeChange -> notifyItemRangeChanged(
adapterIndex,
c.count
)
}
}
}
reset -> {
log.d("notifyChange: DataSetChanged! reason=$reason")
this.list = newList
notifyDataSetChanged()
}
else -> {
val diffResult = DiffUtil.calculateDiff(
DiffCallback(
oldList = this.list, // 比較対象の古いデータ
newList = newList,
biasListIndex = column.toListIndex(0)
),
false // ログを見た感じ、移動なんてなかった
)
val time = SystemClock.elapsedRealtime() - timeStart
log.d("notifyChange: size=${newList.size},time=${time}ms,reason=$reason")
this.list = newList
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
log.d("notifyChange: notifyItemRangeInserted offset=$position,count=$count")
notifyItemRangeInserted(position, count)
}
override fun onRemoved(position: Int, count: Int) {
log.d("notifyChange: notifyItemRangeRemoved offset=$position,count=$count")
notifyItemRangeRemoved(position, count)
}
override fun onChanged(position: Int, count: Int, payload: Any?) {
log.d("notifyChange: notifyItemRangeChanged offset=$position,count=$count")
notifyItemRangeChanged(position, count, payload)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
log.d("notifyChange: notifyItemMoved from=$fromPosition,to=$toPosition")
notifyItemMoved(fromPosition, toPosition)
}
})
}
}
// diffを取る部分をワーカースレッドで実行したいが、直後にスクロール位置の処理があるので差支えがある…
}
}