質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

1回答

2809閲覧

RecyclerView で複数タイプの ViewHolder を追加・削除する方法

merrill

総合スコア1

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2021/07/24 04:37

編集2021/07/24 05:33

前提・実現したいこと

Android Studio の RecyclerView で複数タイプの ViewHolder を追加したり削除したりしても問題なく表示できるようにしたい。

発生している問題・エラーメッセージ

RecyclerView の View をタップするとそのViewが削除されるような機能があるとして、下記の Adapter で定義した remove メソッドで削除すると onBindViewHolder で position と ViewHolder の情報が合わなくなりうまく表示できません。例として 9: Red の View をタップして削除すると、2枚目の添付画像のように 10: Orange が下に2つ表示されてしまいます。

該当のソースコード

MainActivity.kt

kotlin

1class MainActivity : AppCompatActivity() { 2 private lateinit var binding: ActivityMainBinding 3 private var adapter = ListAdapter() 4 5 override fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 binding = ActivityMainBinding.inflate(layoutInflater) 8 setContentView(binding.root) 9 10 // 30個のデータ生成 11 var colors = mutableListOf<Color>() 12 13 for (i in 1..30) { 14 val color = Color() 15 color.id = i 16 17 if (i % 3 == 0) { 18 color.type = ColorType.RED 19 } 20 if (i % 3 == 1) { 21 color.type = ColorType.ORANGE 22 } 23 if (i % 3 == 2) { 24 color.type = ColorType.YELLOW 25 } 26 27 colors.add(color) 28 } 29 30 adapter.models = colors 31 32 binding.list.adapter = adapter 33 binding.list.layoutManager = LinearLayoutManager(this) 34 } 35}

 

ListAdapter.kt

kotlin

1package com.example.tableapp 2 3import android.graphics.drawable.ColorDrawable 4import android.view.LayoutInflater 5import android.view.View 6import android.view.ViewGroup 7import android.widget.TextView 8import androidx.recyclerview.widget.RecyclerView 9 10class Color { 11 var id: Int = 0 12 var type: ColorType = ColorType.RED 13} 14 15enum class ColorType(val colorName: String, val code: String) { 16 RED("Red", "#c0392b"), 17 ORANGE("Orange", "#e67e22"), 18 YELLOW("Yellow", "#f1c40f") 19} 20 21sealed class ListViewHolder(view: View) : RecyclerView.ViewHolder(view) { 22 companion object { 23 operator fun invoke(color: Color, inflater: LayoutInflater, parent: ViewGroup) = 24 when (color.type) { 25 ColorType.RED -> { 26 RedView(inflater.inflate(R.layout.fragment_red, parent, false)) 27 } 28 ColorType.ORANGE -> { 29 OrangeView(inflater.inflate(R.layout.fragment_orange, parent, false)) 30 } 31 else -> { 32 YellowView(inflater.inflate(R.layout.fragment_yellow, parent, false)) 33 } 34 } 35 } 36} 37 38class RedView(view: View) : ListViewHolder(view) { 39 val titleView: TextView = view.findViewById(R.id.text) 40 41 fun bind(color: Color, onClick: (View) -> Unit) { 42 titleView.text = "${color.id}: Red" 43 itemView.background = ColorDrawable(android.graphics.Color.parseColor(color.type.code)) 44 itemView.setOnClickListener(onClick) 45 } 46} 47 48class OrangeView(view: View) : ListViewHolder(view) { 49 val titleView: TextView = view.findViewById(R.id.text) 50 51 fun bind(color: Color, onClick: (View) -> Unit) { 52 titleView.text = "${color.id}: Orange" 53 itemView.background = ColorDrawable(android.graphics.Color.parseColor(color.type.code)) 54 itemView.setOnClickListener(onClick) 55 } 56} 57 58class YellowView(view: View) : ListViewHolder(view) { 59 val titleView: TextView = view.findViewById(R.id.text) 60 61 fun bind(color: Color, onClick: (View) -> Unit) { 62 titleView.text = "${color.id}: Yellow" 63 itemView.background = ColorDrawable(android.graphics.Color.parseColor(color.type.code)) 64 itemView.setOnClickListener(onClick) 65 } 66} 67 68class ListAdapter : RecyclerView.Adapter<ListViewHolder>() { 69 var models = mutableListOf<Color>() 70 var selectItemListener: ((position: Int) -> Unit)? = null 71 72 init { 73 setHasStableIds(true) 74 } 75 76 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder { 77 val inflater = LayoutInflater.from(parent.context) 78 val color: Color = models[viewType] 79 80 return ListViewHolder(color, inflater, parent) 81 } 82 83 override fun onBindViewHolder(holder: ListViewHolder, position: Int) { 84 val color: Color = models[position] 85 86 when (color.type) { 87 ColorType.RED -> { 88 if (holder is RedView) { 89 holder.bind(color, onClick = { 90 remove(holder.layoutPosition) 91 }) 92 } 93 } 94 ColorType.ORANGE -> { 95 if (holder is OrangeView) { 96 holder.bind(color, onClick = { 97 remove(holder.layoutPosition) 98 }) 99 } 100 } 101 ColorType.YELLOW -> { 102 if (holder is YellowView) { 103 holder.bind(color, onClick = { 104 remove(holder.layoutPosition) 105 }) 106 } 107 } 108 else -> { 109 } 110 } 111 } 112 113 override fun getItemId(position: Int): Long { 114 val color = models[position] 115 return color.id.toLong() 116 } 117 118 override fun getItemViewType(position: Int): Int = position 119 override fun getItemCount(): Int = models.size 120 121 fun add(color: Color, position: Int) { 122 models.add(position, color) 123 notifyItemInserted(position) 124 } 125 126 fun remove(position: Int) { 127 models.removeAt(position) 128 notifyItemRemoved(position) 129 } 130}

【1枚目】

イメージ説明

【2枚目】

イメージ説明

試したこと

RecyclerView のリストの追加・削除方法は一通り試したと思うのですがどれもうまくいかず。notifyDataSetChanged でもだめでした。

そもそも上記の削除方法が間違っているのか、RecyclerView の使い方が間違っているのかもわかっていないので、よろしければ教えていただけると助かります。

補足情報(FW/ツールのバージョンなど)

Android Studio 4.2.2

追記

activity_main.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".MainActivity"> 8 9 <androidx.recyclerview.widget.RecyclerView 10 android:id="@+id/list" 11 android:layout_width="0dp" 12 android:layout_height="0dp" 13 app:layout_constraintBottom_toBottomOf="parent" 14 app:layout_constraintEnd_toEndOf="parent" 15 app:layout_constraintStart_toStartOf="parent" 16 app:layout_constraintTop_toTopOf="parent" /> 17</androidx.constraintlayout.widget.ConstraintLayout>

fragment_red.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:id="@+id/constraintLayout" 6 android:layout_width="match_parent" 7 android:layout_height="60dp" 8 tools:context=".RedFragment"> 9 10 <!-- TODO: Update blank fragment layout --> 11 <TextView 12 android:id="@+id/text" 13 android:layout_width="0dp" 14 android:layout_height="wrap_content" 15 android:text="@string/hello_blank_fragment" 16 app:layout_constraintEnd_toEndOf="parent" 17 app:layout_constraintStart_toStartOf="parent" 18 app:layout_constraintTop_toTopOf="parent" /> 19 20</androidx.constraintlayout.widget.ConstraintLayout>

fragment_orange.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:id="@+id/constraintLayout" 6 android:layout_width="match_parent" 7 android:layout_height="60dp" 8 tools:context=".OrangeFragment"> 9 10 <!-- TODO: Update blank fragment layout --> 11 <TextView 12 android:id="@+id/text" 13 android:layout_width="0dp" 14 android:layout_height="wrap_content" 15 android:text="@string/hello_blank_fragment" 16 app:layout_constraintEnd_toEndOf="parent" 17 app:layout_constraintStart_toStartOf="parent" 18 app:layout_constraintTop_toTopOf="parent" /> 19 20</androidx.constraintlayout.widget.ConstraintLayout>

fragment_yellow.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:id="@+id/constraintLayout" 6 android:layout_width="match_parent" 7 android:layout_height="60dp" 8 tools:context=".YellowFragment"> 9 10 <!-- TODO: Update blank fragment layout --> 11 <TextView 12 android:id="@+id/text" 13 android:layout_width="0dp" 14 android:layout_height="wrap_content" 15 android:text="@string/hello_blank_fragment" 16 app:layout_constraintEnd_toEndOf="parent" 17 app:layout_constraintStart_toStartOf="parent" 18 app:layout_constraintTop_toTopOf="parent" /> 19 20</androidx.constraintlayout.widget.ConstraintLayout>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2021/07/24 04:56

試してみたいのでレイアウトもご提示願えますでしょうか。
merrill

2021/07/24 05:33

追記しました。よろしくお願いいたします。
jimbe

2021/07/24 07:32

ありがとうございます。
guest

回答1

0

ベストアンサー

※一部クラス名を変える等あちこち弄ったため、パッケージ名を変えてあります。

動かす前に構造を弄ってしまっていたため、確実な問題個所を指摘出来ないのですが、削除する為の "位置" 情報を誤って持っていた感じかと思います。
Holder に削除するための position を持っていてその位置のデータを remove するようになっていましたが、それだと、例えば 1~10 のデータがありそれぞれが 1~10 の位置のデータを消すようになっていたとして、データ2を消した場合、3以降のデータの位置が繰り上がるので、次にデータ3を消すとしたら2番目を消さなければなりませんが3番目を消してしまうことになります。
このような感じで削除に"位置"を使うのはズレの影響が面倒になりますので、データそのものや id を使って削除するほうが簡単かと思います。

また、ViewType は Holder の種類を示しまして Adapter によって View と紐づけて生成・再利用が管理されますので、データの番号等としてしまうと意図しない動きになるかもしれません。
Holder は ColorType に沿った Red/Orange/Yerrow の三種類ですので、それに合わせるようにしてみました。

MainActivity.kt

kotlin

1package com.teratail.q350991 2 3import androidx.appcompat.app.AppCompatActivity 4import android.os.Bundle 5import androidx.recyclerview.widget.LinearLayoutManager 6import com.teratail.q350991.databinding.ActivityMainBinding 7 8class MainActivity : AppCompatActivity() { 9 private lateinit var binding: ActivityMainBinding 10 private var adapter = ListAdapter() 11 12 override fun onCreate(savedInstanceState: Bundle?) { 13 super.onCreate(savedInstanceState) 14 binding = ActivityMainBinding.inflate(layoutInflater) 15 setContentView(binding.root) 16 17 // 30個のデータ生成 18 var colors = mutableListOf<Data>() 19 20 for (i in 1..30) { 21 val data = Data() 22 data.id = i 23 24 if (i % 3 == 0) { 25 data.type = ColorType.RED 26 } 27 if (i % 3 == 1) { 28 data.type = ColorType.ORANGE 29 } 30 if (i % 3 == 2) { 31 data.type = ColorType.YELLOW 32 } 33 34 colors.add(data) 35 } 36 37 adapter.datas = colors 38 39 binding.list.adapter = adapter 40 binding.list.layoutManager = LinearLayoutManager(this) 41 } 42}

ListAdapter.kt

kotlin

1package com.teratail.q350991 2 3import android.graphics.drawable.ColorDrawable 4import android.view.LayoutInflater 5import android.view.View 6import android.view.ViewGroup 7import android.widget.TextView 8import androidx.recyclerview.widget.RecyclerView 9 10class Data { 11 var id: Int = 0 12 var type: ColorType = ColorType.RED 13} 14interface DataHolder { 15 fun bind(data: Data) 16} 17 18enum class ColorType(val colorName: String, val code: String) { 19 RED("Red", "#c0392b"), 20 ORANGE("Orange", "#e67e22"), 21 YELLOW("Yellow", "#f1c40f") 22} 23 24class ListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { 25 var datas = mutableListOf<Data>() 26 27 init { 28 setHasStableIds(true) 29 } 30 31 inner class RedHolder(view: View) : RecyclerView.ViewHolder(view), DataHolder { 32 val titleView: TextView = view.findViewById(R.id.text) 33 lateinit var data: Data 34 init { 35 itemView.setOnClickListener { remove(data) } 36 } 37 override fun bind(data: Data) { 38 this.data = data 39 titleView.text = "${data.id}: Red" 40 itemView.background = ColorDrawable(android.graphics.Color.parseColor(data.type.code)) 41 } 42 } 43 44 inner class OrangeHolder(view: View) : RecyclerView.ViewHolder(view), DataHolder { 45 val titleView: TextView = view.findViewById(R.id.text) 46 lateinit var data: Data 47 init { 48 itemView.setOnClickListener { remove(data) } 49 } 50 override fun bind(data: Data) { 51 this.data = data 52 titleView.text = "${data.id}: Orange" 53 itemView.background = ColorDrawable(android.graphics.Color.parseColor(data.type.code)) 54 } 55 } 56 57 inner class YellowHolder(view: View) : RecyclerView.ViewHolder(view), DataHolder { 58 val titleView: TextView = view.findViewById(R.id.text) 59 lateinit var data: Data 60 init { 61 itemView.setOnClickListener { remove(data) } 62 } 63 override fun bind(data: Data) { 64 this.data = data 65 titleView.text = "${data.id}: Yellow" 66 itemView.background = ColorDrawable(android.graphics.Color.parseColor(data.type.code)) 67 } 68 } 69 70 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 71 val inflater = LayoutInflater.from(parent.context) 72 val colorType = ColorType.values()[viewType]; 73 when (colorType) { 74 ColorType.RED -> { 75 return RedHolder(inflater.inflate(R.layout.fragment_red, parent, false)) 76 } 77 ColorType.ORANGE -> { 78 return OrangeHolder(inflater.inflate(R.layout.fragment_orange, parent, false)) 79 } 80 } 81 return YellowHolder(inflater.inflate(R.layout.fragment_yellow, parent, false)) 82 } 83 84 override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 85 if (holder is DataHolder) { 86 holder.bind(datas[position]) 87 } 88 } 89 90 override fun getItemId(position: Int): Long { 91 val data = datas[position] 92 return data.id.toLong() 93 } 94 95 override fun getItemViewType(position: Int): Int = datas[position].type.ordinal 96 override fun getItemCount(): Int = datas.size 97 98 fun add(data: Data, position: Int) { 99 datas.add(position, data) 100 notifyItemInserted(position) 101 } 102 103 fun remove(data: Data) { 104 datas.remove(data) 105 notifyDataSetChanged(); 106 } 107}

投稿2021/07/24 07:31

jimbe

総合スコア12625

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

merrill

2021/07/24 08:01

動きました!ありがとうございます。 > ViewType は Holder の種類を示しまして Adapter によって View と紐づけて生成・再利用が管理されます ここの理解ができてなかったのでずっとはまってました。助かりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問