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 ) : androidx.recyclerview.widget.RecyclerView.Adapter() { companion object { private val log = LogCategory("ItemListAdapter") } private inner class DiffCallback( val oldList : List, val newList : List, 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 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.headerType if(headerType == null || position > 0) return 0 return headerType.viewType } override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : androidx.recyclerview.widget.RecyclerView.ViewHolder { when(viewType) { 0 -> { val holder = ItemViewHolder(activity) holder.viewRoot.tag = holder return ViewHolderItem(holder) } Column.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 } Column.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 } Column.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 } Column.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 } else -> throw RuntimeException("unknown viewType: $viewType") } } fun findHeaderViewHolder(listView : androidx.recyclerview.widget.RecyclerView) : ViewHolderHeaderBase? { return when(column.headerType) { null -> null else -> listView.findViewHolderForAdapterPosition(0) as? ViewHolderHeaderBase } } override fun onBindViewHolder(holder : androidx.recyclerview.widget.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 : androidx.recyclerview.widget.RecyclerView.ViewHolder) { if(holder is ViewHolderItem) { holder.ivh.onViewRecycled() } else if(holder is ViewHolderHeaderBase) { holder.onViewRecycled() } } fun notifyChange( reason : String, changeList : List? = null, reset : Boolean = false ) { val time_start = SystemClock.elapsedRealtime() // カラムから最新データをコピーする val new_list = ArrayList() 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.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を取る部分をワーカースレッドで実行したいが、直後にスクロール位置の処理があるので差支えがある… } }