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

248 lines
7.7 KiB
Kotlin

package jp.juggler.subwaytooter
import android.os.Handler
import android.os.SystemClock
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.util.LogCategory
internal 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 inner 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.list_data.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 {
when(viewType) {
0 -> {
val holder = ItemViewHolder(activity)
holder.viewRoot.tag = holder
return ViewHolderItem(holder)
}
HeaderType.Profile.viewType -> {
val viewRoot =
activity.layoutInflater.inflate(R.layout.lv_header_profile, parent, false)
val holder = ViewHolderHeaderProfile(activity, viewRoot)
viewRoot.tag = holder
return holder
}
HeaderType.Search.viewType -> {
val viewRoot =
activity.layoutInflater.inflate(R.layout.lv_header_search_desc, parent, false)
val holder = ViewHolderHeaderSearch(activity, viewRoot)
viewRoot.tag = holder
return holder
}
HeaderType.Instance.viewType -> {
val viewRoot =
activity.layoutInflater.inflate(R.layout.lv_header_instance, parent, false)
val holder = ViewHolderHeaderInstance(activity, viewRoot)
viewRoot.tag = holder
return holder
}
HeaderType.Filter.viewType ->{
val viewRoot =
activity.layoutInflater.inflate(R.layout.lv_header_filter, parent, false)
val holder = ViewHolderHeaderFilter(activity, viewRoot)
viewRoot.tag = holder
return holder
}
HeaderType.ProfileDirectory.viewType ->{
val viewRoot =
activity.layoutInflater.inflate(R.layout.lv_header_profile_directory, parent, false)
val holder = ViewHolderHeaderProfileDirectory(activity, viewRoot)
viewRoot.tag = holder
return holder
}
else -> throw RuntimeException("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()
}
}
fun notifyChange(
reason : String,
changeList : List<AdapterChange>? = null,
reset : Boolean = false
) {
val time_start = SystemClock.elapsedRealtime()
// カラムから最新データをコピーする
val new_list = ArrayList<TimelineItem>()
new_list.ensureCapacity(column.list_data.size)
new_list.addAll(column.list_data)
when {
// 変更リストが指定された場合はヘッダ部分と変更部分を通知する
changeList != null -> {
log.d("notifyChange: changeList=${changeList.size},reason=$reason")
this.list = new_list
// ヘッダは毎回更新する
// (ヘッダだけ更新するためにカラの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 = new_list
notifyDataSetChanged()
}
else -> {
val diffResult = DiffUtil.calculateDiff(
DiffCallback(
oldList = this.list, // 比較対象の古いデータ
newList = new_list,
biasListIndex = column.toListIndex(0)
),
false // ログを見た感じ、移動なんてなかった
)
val time = SystemClock.elapsedRealtime() - time_start
log.d("notifyChange: size=${new_list.size},time=${time}ms,reason=$reason")
this.list = new_list
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を取る部分をワーカースレッドで実行したいが、直後にスクロール位置の処理があるので差支えがある…
}
}