Update ViewHolder structure

Move all shared viewholders to /recycler/, and change structure of shared viewholders to prohibit direct instantiation.
This commit is contained in:
OxygenCobalt 2020-10-03 19:29:03 -06:00
parent 45f411fdd7
commit 2126b4f78f
15 changed files with 186 additions and 166 deletions

View file

@ -67,6 +67,8 @@ class AlbumDetailFragment : Fragment() {
// If the album was shown directly from LibraryFragment, Then enable the ability to // If the album was shown directly from LibraryFragment, Then enable the ability to
// navigate upwards to the parent artist // navigate upwards to the parent artist
if (args.enableParentNav) { if (args.enableParentNav) {
detailModel.doneWithNavToParent()
detailModel.navToParent.observe(viewLifecycleOwner) { detailModel.navToParent.observe(viewLifecycleOwner) {
if (it) { if (it) {
findNavController().navigate( findNavController().navigate(

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailAlbumAdapter( class DetailAlbumAdapter(
private val listener: ClickListener<Album> private val listener: ClickListener<Album>
@ -23,7 +23,7 @@ class DetailAlbumAdapter(
holder.bind(getItem(position)) holder.bind(getItem(position))
} }
// Generic ViewHolder for an album // Generic ViewHolder for a detail album
inner class ViewHolder( inner class ViewHolder(
private val binding: ItemArtistAlbumBinding private val binding: ItemArtistAlbumBinding
) : BaseViewHolder<Album>(binding, listener) { ) : BaseViewHolder<Album>(binding, listener) {

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemGenreArtistBinding import org.oxycblt.auxio.databinding.ItemGenreArtistBinding
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailArtistAdapter( class DetailArtistAdapter(
private val listener: ClickListener<Artist> private val listener: ClickListener<Artist>

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailSongAdapter( class DetailSongAdapter(
private val listener: ClickListener<Song> private val listener: ClickListener<Song>

View file

@ -23,7 +23,7 @@ class LibraryViewModel : ViewModel() {
val searchHasFocus: Boolean get() = mSearchHasFocus val searchHasFocus: Boolean get() = mSearchHasFocus
// TODO: Move these to prefs when they're added // TODO: Move these to prefs when they're added
private val mShowMode = MutableLiveData(SHOW_ARTISTS) private val mShowMode = MutableLiveData(SHOW_ALBUMS)
val showMode: LiveData<Int> get() = mShowMode val showMode: LiveData<Int> get() = mShowMode
private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN) private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN)

View file

@ -1,21 +1,21 @@
package org.oxycblt.auxio.library.recycler package org.oxycblt.auxio.library.recycler
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
import org.oxycblt.auxio.theme.SHOW_ALBUMS import org.oxycblt.auxio.theme.SHOW_ALBUMS
import org.oxycblt.auxio.theme.SHOW_ARTISTS import org.oxycblt.auxio.theme.SHOW_ARTISTS
import org.oxycblt.auxio.theme.SHOW_GENRES import org.oxycblt.auxio.theme.SHOW_GENRES
// A Great Value androidx ListAdapter that can display three types of ViewHolders. // A ListAdapter that can contain three different types of ViewHolders depending
// the showmode given. It cannot display multiple types of viewholders *at once*.
class LibraryAdapter( class LibraryAdapter(
private val showMode: Int, private val showMode: Int,
val listener: ClickListener<BaseModel> val listener: ClickListener<BaseModel>
@ -39,36 +39,21 @@ class LibraryAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// Return a different View Holder depending on the show type // Return a different View Holder depending on the show type
return when (showMode) { return when (showMode) {
SHOW_GENRES -> GenreViewHolder( SHOW_GENRES -> GenreViewHolder.from(parent.context, listener)
listener, SHOW_ARTISTS -> ArtistViewHolder.from(parent.context, listener)
ItemGenreBinding.inflate(LayoutInflater.from(parent.context)) SHOW_ALBUMS -> AlbumViewHolder.from(parent.context, listener)
) else -> ArtistViewHolder.from(parent.context, listener)
SHOW_ARTISTS -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(LayoutInflater.from(parent.context))
)
SHOW_ALBUMS -> AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(LayoutInflater.from(parent.context))
)
else -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(LayoutInflater.from(parent.context))
)
} }
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (showMode) { when (showMode) {
SHOW_GENRES -> holder as GenreViewHolder SHOW_GENRES -> (holder as GenreViewHolder).bind(data[position] as Genre)
SHOW_ARTISTS -> holder as ArtistViewHolder SHOW_ARTISTS -> (holder as ArtistViewHolder).bind(data[position] as Artist)
SHOW_ALBUMS -> holder as AlbumViewHolder SHOW_ALBUMS -> (holder as AlbumViewHolder).bind(data[position] as Album)
else -> return else -> return
}.bind(data[position]) }
} }
// Update the data, as its an internal value. // Update the data, as its an internal value.

View file

@ -1,14 +1,8 @@
package org.oxycblt.auxio.library.recycler package org.oxycblt.auxio.library.recycler
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
@ -17,62 +11,34 @@ import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
class SearchAdapter( class SearchAdapter(
private val listener: ClickListener<BaseModel> private val listener: ClickListener<BaseModel>
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback<BaseModel>()) { ) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback<BaseModel>()) {
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (getItem(position)) { return when (getItem(position)) {
is Genre -> ITEM_TYPE_GENRE is Genre -> GenreViewHolder.ITEM_TYPE
is Artist -> ITEM_TYPE_ARTIST is Artist -> ArtistViewHolder.ITEM_TYPE
is Album -> ITEM_TYPE_ALBUM is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> ITEM_TYPE_SONG is Song -> SongViewHolder.ITEM_TYPE
is Header -> ITEM_TYPE_HEADER is Header -> HeaderViewHolder.ITEM_TYPE
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
ITEM_TYPE_GENRE -> GenreViewHolder( GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(parent.context, listener)
listener, ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(parent.context, listener)
ItemGenreBinding.inflate( AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(parent.context, listener)
LayoutInflater.from(parent.context) SongViewHolder.ITEM_TYPE -> SongViewHolder.from(parent.context, listener)
) HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
)
ITEM_TYPE_ARTIST -> ArtistViewHolder( else -> HeaderViewHolder.from(parent.context)
listener,
ItemArtistBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_ALBUM -> AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_SONG -> SongViewHolder(
listener,
ItemSongBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_HEADER -> HeaderViewHolder(
ItemHeaderBinding.inflate(
LayoutInflater.from(parent.context)
)
)
else -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(
LayoutInflater.from(parent.context)
)
)
} }
} }
@ -85,6 +51,6 @@ class SearchAdapter(
is HeaderViewHolder -> holder is HeaderViewHolder -> holder
else -> return else -> return
}.onBind(getItem(position)) }.bind(getItem(position))
} }
} }

View file

@ -1,75 +0,0 @@
package org.oxycblt.auxio.library.recycler
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
const val ITEM_TYPE_GENRE = 10
const val ITEM_TYPE_ARTIST = 11
const val ITEM_TYPE_ALBUM = 12
const val ITEM_TYPE_SONG = 13
const val ITEM_TYPE_HEADER = 14
class GenreViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemGenreBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.genre = model as Genre
binding.genreName.requestLayout()
}
}
class ArtistViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemArtistBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.artist = model as Artist
binding.artistName.requestLayout()
}
}
class AlbumViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemAlbumBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.album = model as Album
binding.albumName.requestLayout()
}
}
class SongViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemSongBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.song = model as Song
binding.songName.requestLayout()
binding.songInfo.requestLayout()
}
}
class HeaderViewHolder(
private val binding: ItemHeaderBinding
) : BaseViewHolder<BaseModel>(binding, null) {
override fun onBind(model: BaseModel) {
binding.header = model as Header
}
}

View file

@ -131,7 +131,7 @@ fun TextView.bindArtistCounts(artist: Artist) {
// Get a bunch of miscellaneous album information [Year, Songs, Duration] and combine them // Get a bunch of miscellaneous album information [Year, Songs, Duration] and combine them
@BindingAdapter("albumDetails") @BindingAdapter("albumDetails")
fun TextView.bindAlbumDetails(album: Album) { fun TextView.bindAllAlbumDetails(album: Album) {
text = context.getString( text = context.getString(
R.string.format_double_info, R.string.format_double_info,
album.year.toYear(context), album.year.toYear(context),
@ -143,6 +143,18 @@ fun TextView.bindAlbumDetails(album: Album) {
) )
} }
@BindingAdapter("albumInfo")
fun TextView.bindAlbumInfo(album: Album) {
text = context.getString(
R.string.format_info,
album.artist.name,
context.resources.getQuantityString(
R.plurals.format_song_count,
album.numSongs, album.numSongs
)
)
}
@BindingAdapter("albumYear") @BindingAdapter("albumYear")
fun TextView.bindAlbumDate(album: Album) { fun TextView.bindAlbumDate(album: Album) {
text = album.year.toYear(context) text = album.year.toYear(context)

View file

@ -4,7 +4,7 @@ import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
// RecyclerView click listener // RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit) class ClickListener<T : BaseModel>(val onClick: (T) -> Unit)
// Base Diff callback // Base Diff callback
class DiffCallback<T : BaseModel> : DiffUtil.ItemCallback<T>() { class DiffCallback<T : BaseModel> : DiffUtil.ItemCallback<T>() {

View file

@ -1,10 +1,12 @@
package org.oxycblt.auxio.recycler package org.oxycblt.auxio.recycler.viewholders
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.recycler.ClickListener
// ViewHolder abstraction that automates some of the things that are common for all ViewHolders. // ViewHolder abstraction that automates some of the things that are common for all ViewHolders.
// TODO: Implement some kind of abstraction for BaseViewHolder.from()
abstract class BaseViewHolder<T : BaseModel>( abstract class BaseViewHolder<T : BaseModel>(
private val baseBinding: ViewDataBinding, private val baseBinding: ViewDataBinding,
protected val listener: ClickListener<T>? protected val listener: ClickListener<T>?

View file

@ -0,0 +1,127 @@
package org.oxycblt.auxio.recycler.viewholders
import android.content.Context
import android.view.LayoutInflater
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.ClickListener
// Basic ViewHolders for each music model.
// FIXME: Mode these type signaturs to something sensible.
class GenreViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemGenreBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.genre = model as Genre
binding.genreName.requestLayout()
}
companion object {
const val ITEM_TYPE = 10
fun from(context: Context, listener: ClickListener<BaseModel>): GenreViewHolder {
return GenreViewHolder(
ClickListener { listener.onClick(it) },
ItemGenreBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class ArtistViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemArtistBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.artist = model as Artist
binding.artistName.requestLayout()
}
companion object {
const val ITEM_TYPE = 11
fun from(context: Context, listener: ClickListener<BaseModel>): ArtistViewHolder {
return ArtistViewHolder(
ClickListener { listener.onClick(it) },
ItemArtistBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class AlbumViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemAlbumBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.album = model as Album
binding.albumName.requestLayout()
}
companion object {
const val ITEM_TYPE = 12
fun from(context: Context, listener: ClickListener<BaseModel>): AlbumViewHolder {
return AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class SongViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemSongBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.song = model as Song
binding.songName.requestLayout()
binding.songInfo.requestLayout()
}
companion object {
const val ITEM_TYPE = 13
fun from(context: Context, listener: ClickListener<BaseModel>): SongViewHolder {
return SongViewHolder(
listener,
ItemSongBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class HeaderViewHolder(
private val binding: ItemHeaderBinding
) : BaseViewHolder<BaseModel>(binding, null) {
override fun onBind(model: BaseModel) {
binding.header = model as Header
}
companion object {
const val ITEM_TYPE = 14
fun from(context: Context): HeaderViewHolder {
return HeaderViewHolder(
ItemHeaderBinding.inflate(LayoutInflater.from(context))
)
}
}
}

View file

@ -5,8 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class SongAdapter( class SongAdapter(
private val data: List<Song>, private val data: List<Song>,

View file

@ -1,6 +1,7 @@
package org.oxycblt.auxio.theme package org.oxycblt.auxio.theme
// Preference Constants // Preference Constants
// TODO: Move these to dedicated enum class.
const val SHOW_ARTISTS = 0 const val SHOW_ARTISTS = 0
const val SHOW_ALBUMS = 1 const val SHOW_ALBUMS = 1
const val SHOW_GENRES = 2 const val SHOW_GENRES = 2

View file

@ -39,7 +39,7 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
app:layout_constraintBottom_toTopOf="@+id/album_song_count" app:layout_constraintBottom_toTopOf="@+id/album_info"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -47,14 +47,14 @@
tools:text="Album Name" /> tools:text="Album Name" />
<TextView <TextView
android:id="@+id/album_song_count" android:id="@+id/album_info"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium" android:layout_marginStart="@dimen/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItemSecondary" android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:maxLines="1" android:maxLines="1"
app:albumSongCount="@{album}" app:albumInfo="@{album}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_name" app:layout_constraintTop_toBottomOf="@+id/album_name"