RecyclerViewでリストを角丸にしたくなる場合があります。
安直にはRecyclerView全体を角丸にしてしまう方法もありますが、以下のように、リスト全体の上下左右以外も角丸にしたい場合、各要素の位置に応じて角丸を設定する必要があります。
角丸の付け方はいろいろあると思いますが、制限はあるもののbackgroundに設定するdrawableを変更するのが簡単でしょうか。
GradientDrawableで角丸とマージン要素含めて定義しておいて、背景のdrawableを切り替えるだけで良いようにしてみます。
layer-listでitemにoffsetを設定し、shapeのpaddingに同じ値を設定することでそれっぽく定義できます。この場合、marginではなくpaddingなので要素サイズへの影響を考える必要があります。
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:end="8dp" android:start="8dp" android:top="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="16dp" />
<padding android:end="8dp" android:start="8dp" android:top="8dp"/>
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:bottom="1dp" android:end="8dp" android:start="8dp" android:top="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:topLeftRadius="16dp" android:topRightRadius="16dp"/>
<padding android:bottom="1dp" android:end="8dp" android:start="8dp" android:top="8dp"/>
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:bottom="1dp" android:end="8dp" android:start="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<padding android:bottom="1dp" android:end="8dp" android:start="8dp"/>
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:bottom="8dp" android:start="8dp" android:end="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/>
<padding android:bottom="8dp" android:start="8dp" android:end="8dp"/>
</shape>
</item>
</layer-list>
ってことで、onBindViewHolderでpositionに応じてbackgroundを変更します。
private class HogeAdapter(activity: Activity) : ListAdapter<Int, ItemViewHolder>(
object : DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean = oldItem == newItem
}
) {
private val inflater = activity.layoutInflater
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
ItemViewHolder(ItemContentBinding.inflate(inflater, parent, false))
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.binding.text.text = "item: " + getItem(position)
holder.binding.root.setBackgroundResource(
when (position) {
0 -> R.drawable.bg_top
1 -> R.drawable.bg_header
itemCount - 1 -> R.drawable.bg_footer
else -> R.drawable.bg_middle
}
)
}
}
しかし、要素を更新したとき、単に順序が変化しただけでは、onBindViewHolder
がコールされないという問題があります。
表示位置が変化したことを受け取れるコールバックはなさそうです。
対応方法
位置が変化したことを直接受け取ることはできないので、この背景要素もコンテンツの一部として、要素リストにパラメータを追加して、位置が変化し、背景を変更する必要がある場合にonBindViewHolder
がコールされるようにします。
どの背景を指定するのかという情報を追加したdataクラスをつくって、
private enum class Background {
TOP,
HEADER,
MIDDLE,
FOOTER,
}
private data class Item(
val background: Background,
val id: Int,
)
リストの順序に応じてこの値を追加したものをAdapterにsubmitListします。
private fun makeItemList(): List<Item> =
makeIdList().let { list ->
list.mapIndexed { index, id ->
Item(
when (index) {
0 -> Background.TOP
1 -> Background.HEADER
list.lastIndex -> Background.FOOTER
else -> Background.MIDDLE
}, id
)
}
}
ListAdapterを以下のように実装し、DiffUtil.ItemCallbackで背景が変化した場合もコンテンツの変化として扱うようにします。
private class FugaAdapter(activity: Activity) : ListAdapter<Item, ItemViewHolder>(
object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem == newItem
}
) {
private val inflater = activity.layoutInflater
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
ItemViewHolder(ItemContentBinding.inflate(inflater, parent, false))
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.binding.text.text = "item: " + getItem(position).id
holder.binding.root.setBackgroundResource(
when (getItem(position).background) {
Background.TOP -> R.drawable.bg_top
Background.HEADER -> R.drawable.bg_header
Background.FOOTER -> R.drawable.bg_footer
Background.MIDDLE -> R.drawable.bg_middle
}
)
}
}
これで、リスト内の要素がシャッフルされた場合も、ちゃんと背景が位置に応じて変化するようになります。
以上です。