recycler: add span size abstraction to adapter

Make adapter instances handle span sizes.

This supercedes the hackier solution where the fragments would have to
reference adapter data in order to determine span size. Not anymore.
This commit is contained in:
Alexander Capehart 2022-09-02 13:35:59 -06:00
parent acaf679000
commit be8623ad2d
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 50 additions and 43 deletions

View file

@ -41,7 +41,6 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
@ -84,13 +83,7 @@ class AlbumDetailFragment :
setOnMenuItemClickListener(this@AlbumDetailFragment) setOnMenuItemClickListener(this@AlbumDetailFragment)
} }
binding.detailRecycler.apply { binding.detailRecycler.adapter = detailAdapter
adapter = detailAdapter
setSpanSizeLookup { pos ->
val item = detailAdapter.currentList[pos]
item is Album || item is Header || item is SortHeader
}
}
// -- VIEWMODEL SETUP --- // -- VIEWMODEL SETUP ---

View file

@ -39,7 +39,6 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -79,13 +78,7 @@ class ArtistDetailFragment :
setOnMenuItemClickListener(this@ArtistDetailFragment) setOnMenuItemClickListener(this@ArtistDetailFragment)
} }
binding.detailRecycler.apply { binding.detailRecycler.adapter = detailAdapter
adapter = detailAdapter
setSpanSizeLookup { pos ->
val item = detailAdapter.currentList[pos]
item is Artist || item is Header || item is SortHeader
}
}
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---

View file

@ -40,7 +40,6 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -80,13 +79,7 @@ class GenreDetailFragment :
setOnMenuItemClickListener(this@GenreDetailFragment) setOnMenuItemClickListener(this@GenreDetailFragment)
} }
binding.detailRecycler.apply { binding.detailRecycler.adapter = detailAdapter
adapter = detailAdapter
setSpanSizeLookup { pos ->
val item = detailAdapter.currentList[pos]
item is Genre || item is Header || item is SortHeader
}
}
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---

View file

@ -77,6 +77,11 @@ class AlbumDetailAdapter(private val listener: Listener) :
} }
} }
override fun isItemFullWidth(position: Int): Boolean {
val item = differ.currentList[position]
return super.isItemFullWidth(position) || item is Album || item is DiscHeader
}
companion object { companion object {
private val DIFFER = private val DIFFER =
object : SimpleItemCallback<Item>() { object : SimpleItemCallback<Item>() {

View file

@ -78,6 +78,11 @@ class ArtistDetailAdapter(private val listener: Listener) :
} }
} }
override fun isItemFullWidth(position: Int): Boolean {
val item = differ.currentList[position]
return super.isItemFullWidth(position) || item is Artist
}
companion object { companion object {
private val DIFFER = private val DIFFER =
object : SimpleItemCallback<Item>() { object : SimpleItemCallback<Item>() {

View file

@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
import org.oxycblt.auxio.detail.SortHeader import org.oxycblt.auxio.detail.SortHeader
import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
@ -38,7 +39,7 @@ import org.oxycblt.auxio.util.inflater
abstract class DetailAdapter<L : DetailAdapter.Listener>( abstract class DetailAdapter<L : DetailAdapter.Listener>(
private val listener: L, private val listener: L,
diffCallback: DiffUtil.ItemCallback<Item> diffCallback: DiffUtil.ItemCallback<Item>
) : IndicatorAdapter<RecyclerView.ViewHolder>() { ) : IndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
private var isPlaying = false private var isPlaying = false
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size @Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
@ -77,6 +78,11 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
super.onBindViewHolder(holder, position, payloads) super.onBindViewHolder(holder, position, payloads)
} }
override fun isItemFullWidth(position: Int): Boolean {
val item = differ.currentList[position]
return item is Header || item is SortHeader
}
protected val differ = AsyncListDiffer(this, diffCallback) protected val differ = AsyncListDiffer(this, diffCallback)
override val currentList: List<Item> override val currentList: List<Item>

View file

@ -71,6 +71,11 @@ class GenreDetailAdapter(private val listener: Listener) :
} }
} }
override fun isItemFullWidth(position: Int): Boolean {
val item = differ.currentList[position]
return super.isItemFullWidth(position) || item is Genre
}
companion object { companion object {
val DIFFER = val DIFFER =
object : SimpleItemCallback<Item>() { object : SimpleItemCallback<Item>() {

View file

@ -33,9 +33,9 @@ import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.util.getDrawableCompat import org.oxycblt.auxio.util.getDrawableCompat
/** /**
* View that displays the playback indicator. Nominally emulates [StyledImageView], but is * View that displays the playback indicator. Nominally emulates [StyledImageView], but is much
* much different internally as an animated icon can't be wrapped within StyledDrawable without * different internally as an animated icon can't be wrapped within StyledDrawable without causing
* causing insane issues. * insane issues.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class IndicatorView class IndicatorView
@ -45,7 +45,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
private val playingIndicatorDrawable = private val playingIndicatorDrawable =
context.getDrawableCompat(R.drawable.ic_playing_indicator_24) as AnimationDrawable context.getDrawableCompat(R.drawable.ic_playing_indicator_24) as AnimationDrawable
private val pausedIndicatorDrawable = context.getDrawableCompat(R.drawable.ic_paused_indicator_24) private val pausedIndicatorDrawable =
context.getDrawableCompat(R.drawable.ic_paused_indicator_24)
private val indicatorMatrix = Matrix() private val indicatorMatrix = Matrix()
private val indicatorMatrixSrc = RectF() private val indicatorMatrixSrc = RectF()

View file

@ -26,6 +26,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
import org.oxycblt.auxio.ui.recycler.GenreViewHolder import org.oxycblt.auxio.ui.recycler.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
@ -36,7 +37,7 @@ import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.ui.recycler.SongViewHolder
class SearchAdapter(private val listener: MenuItemListener) : class SearchAdapter(private val listener: MenuItemListener) :
IndicatorAdapter<RecyclerView.ViewHolder>() { IndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
private val differ = AsyncListDiffer(this, DIFFER) private val differ = AsyncListDiffer(this, DIFFER)
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
@ -79,6 +80,8 @@ class SearchAdapter(private val listener: MenuItemListener) :
} }
} }
override fun isItemFullWidth(position: Int) = differ.currentList[position] is Header
override val currentList: List<Item> override val currentList: List<Item>
get() = differ.currentList get() = differ.currentList

View file

@ -39,7 +39,6 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.util.androidViewModels import org.oxycblt.auxio.util.androidViewModels
@ -104,10 +103,7 @@ class SearchFragment :
} }
} }
binding.searchRecycler.apply { binding.searchRecycler.adapter = searchAdapter
adapter = searchAdapter
setSpanSizeLookup { pos -> searchAdapter.currentList[pos] is Header }
}
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---

View file

@ -54,13 +54,20 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
return insets return insets
} }
// TODO: Move abstraction to adapters since using external data will not work well override fun setAdapter(adapter: Adapter<*>?) {
inline fun setSpanSizeLookup(crossinline fullWidth: (Int) -> Boolean) { super.setAdapter(adapter)
val glm = layoutManager as GridLayoutManager
val spanCount = glm.spanCount if (adapter is SpanSizeLookup) {
val glm = (layoutManager as GridLayoutManager)
glm.spanSizeLookup = glm.spanSizeLookup =
object : GridLayoutManager.SpanSizeLookup() { object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int) = if (fullWidth(position)) spanCount else 1 override fun getSpanSize(position: Int) =
if (adapter.isItemFullWidth(position)) glm.spanCount else 1
} }
} }
} }
interface SpanSizeLookup {
fun isItemFullWidth(position: Int): Boolean
}
}