回答編集履歴

6

追加

2022/10/28 17:20

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -3,6 +3,7 @@
3
3
  質問の画像を見る限り、まだリストビューがスクロールするほどの件数が無い為に再利用が発生していないようですが、再利用が発生した場合、convertView が null で無い=チェックボックスのリスナが古いまま新しいデータの表示に使われ、チェックボックスの変更は古いデータに対して処理が行われることになってしまいます。
4
4
 
5
5
  追加毎にログが出るのは、追加毎にリストに setAdapter し全ての(再)表示が行われて getView が呼ばれるため、デフォルト false のチェックボックスに setChecked で true を設定しているデータで OnCheckedChangeListener が呼ばれている為でしょう。
6
+ それだけなら常に true と出るはずですが・・・なぜ false になるのかは再現出来ていません。
6
7
 
7
8
  ---
8
9
  ```java

5

追加

2022/10/28 17:18

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -1,6 +1,8 @@
1
1
  レイアウトをインフレートした時だけリスナを定義するのでは、問題があります。
2
2
  getView で convertView が null かどうかによって infrate を制御するのは、ListView が convertView を再利用するからです。
3
3
  質問の画像を見る限り、まだリストビューがスクロールするほどの件数が無い為に再利用が発生していないようですが、再利用が発生した場合、convertView が null で無い=チェックボックスのリスナが古いまま新しいデータの表示に使われ、チェックボックスの変更は古いデータに対して処理が行われることになってしまいます。
4
+
5
+ 追加毎にログが出るのは、追加毎にリストに setAdapter し全ての(再)表示が行われて getView が呼ばれるため、デフォルト false のチェックボックスに setChecked で true を設定しているデータで OnCheckedChangeListener が呼ばれている為でしょう。
4
6
 
5
7
  ---
6
8
  ```java

4

コード修正

2022/10/28 16:51

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -82,8 +82,7 @@
82
82
  return position;
83
83
  }
84
84
  @Override
85
- public @NonNull
86
- View getView(int position, View convertView, @NonNull ViewGroup parent) {
85
+ public @NonNull View getView(int position, View convertView, @NonNull ViewGroup parent) {
87
86
  if(convertView == null) {
88
87
  convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list, parent, false);
89
88
  convertView.setTag(new ViewHolder(convertView));

3

誤字

2022/10/28 13:30

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -1,5 +1,5 @@
1
1
  レイアウトをインフレートした時だけリスナを定義するのでは、問題があります。
2
- getView で convertView が null かどうかによって infrate を制御するのは、ListView が convartView を再利用するからです。
2
+ getView で convertView が null かどうかによって infrate を制御するのは、ListView が convertView を再利用するからです。
3
3
  質問の画像を見る限り、まだリストビューがスクロールするほどの件数が無い為に再利用が発生していないようですが、再利用が発生した場合、convertView が null で無い=チェックボックスのリスナが古いまま新しいデータの表示に使われ、チェックボックスの変更は古いデータに対して処理が行われることになってしまいます。
4
4
 
5
5
  ---

2

コード修正

2022/10/28 13:28

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -95,7 +95,7 @@
95
95
  vh.viewCheck.setOnCheckedChangeListener(null); //次の setChecked で古いリスナが実行されないように、先に消す.
96
96
  vh.viewCheck.setChecked(cellDataItem.check);
97
97
  vh.viewCheck.setOnCheckedChangeListener((buttonView, isChecked) -> {
98
- cellDataList.get(position).check = isChecked;
98
+ cellDataItem.check = isChecked;
99
99
  Log.i("Test", "cellDataList["+position+"]のcheckに["+isChecked+"]を入れました");
100
100
  notifyDataSetChanged(); //データが変わったのでリストに再表示を要求
101
101
  });

1

コード追加

2022/10/28 13:18

投稿

jimbe
jimbe

スコア12648

test CHANGED
@@ -1,3 +1,187 @@
1
1
  レイアウトをインフレートした時だけリスナを定義するのでは、問題があります。
2
2
  getView で convertView が null かどうかによって infrate を制御するのは、ListView が convartView を再利用するからです。
3
3
  質問の画像を見る限り、まだリストビューがスクロールするほどの件数が無い為に再利用が発生していないようですが、再利用が発生した場合、convertView が null で無い=チェックボックスのリスナが古いまま新しいデータの表示に使われ、チェックボックスの変更は古いデータに対して処理が行われることになってしまいます。
4
+
5
+ ---
6
+ ```java
7
+ import android.graphics.Color;
8
+ import android.os.*;
9
+ import android.util.Log;
10
+ import android.view.*;
11
+ import android.widget.*;
12
+
13
+ import androidx.annotation.NonNull;
14
+ import androidx.appcompat.app.AppCompatActivity;
15
+ import androidx.lifecycle.*;
16
+
17
+ import java.text.*;
18
+ import java.util.*;
19
+
20
+ public class MainActivity extends AppCompatActivity {
21
+ @Override
22
+ protected void onCreate(Bundle savedInstanceState) {
23
+ super.onCreate(savedInstanceState);
24
+ setContentView(R.layout.activity_main);
25
+
26
+ getSupportActionBar().setTitle(R.string.app_name);
27
+
28
+ ListView listView = findViewById(R.id.list_view);
29
+ ListViewAdapter adapter = new ListViewAdapter();
30
+ listView.setAdapter(adapter);
31
+
32
+ NowTimer nowTimer = new NowTimer(100);
33
+ getLifecycle().addObserver(nowTimer);
34
+
35
+ Counter counter = new Counter(0, 9999);
36
+
37
+ EditText commentText = findViewById(R.id.comment_text);
38
+
39
+ Button addButton = findViewById(R.id.add_button);
40
+ addButton.setOnClickListener(v -> {
41
+ CellData cellData = new CellData(nowTimer.getDate(), counter.getCount(), commentText.getText().toString());
42
+ adapter.add(cellData);
43
+ });
44
+ }
45
+
46
+ // データを保持するクラス
47
+ private static class CellData {
48
+ boolean check = false;
49
+ Date time;
50
+ int count;
51
+ String comment;
52
+
53
+ CellData(Date time, int count, String comment) {
54
+ this.time = time;
55
+ this.count = count;
56
+ this.comment = comment;
57
+ }
58
+ }
59
+
60
+ // カスタムアダプタークラス
61
+ private class ListViewAdapter extends BaseAdapter {
62
+ private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
63
+ private final int[] ROW_BG_COLORS = { Color.rgb(240,240,240), Color.WHITE };
64
+
65
+ private List<CellData> cellDataList = new ArrayList<>();
66
+
67
+ void add(CellData cellData) {
68
+ cellDataList.add(cellData);
69
+ notifyDataSetChanged();
70
+ }
71
+
72
+ @Override
73
+ public int getCount() {
74
+ return cellDataList.size();
75
+ }
76
+ @Override
77
+ public Object getItem(int position) {
78
+ return cellDataList.get(position);
79
+ }
80
+ @Override
81
+ public long getItemId(int position) {
82
+ return position;
83
+ }
84
+ @Override
85
+ public @NonNull
86
+ View getView(int position, View convertView, @NonNull ViewGroup parent) {
87
+ if(convertView == null) {
88
+ convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list, parent, false);
89
+ convertView.setTag(new ViewHolder(convertView));
90
+ }
91
+
92
+ CellData cellDataItem = cellDataList.get(position);
93
+ ViewHolder vh = (ViewHolder)convertView.getTag();
94
+
95
+ vh.viewCheck.setOnCheckedChangeListener(null); //次の setChecked で古いリスナが実行されないように、先に消す.
96
+ vh.viewCheck.setChecked(cellDataItem.check);
97
+ vh.viewCheck.setOnCheckedChangeListener((buttonView, isChecked) -> {
98
+ cellDataList.get(position).check = isChecked;
99
+ Log.i("Test", "cellDataList["+position+"]のcheckに["+isChecked+"]を入れました");
100
+ notifyDataSetChanged(); //データが変わったのでリストに再表示を要求
101
+ });
102
+ vh.viewTime.setText(dateFormat.format(cellDataItem.time));
103
+ vh.viewCount.setText("" + cellDataItem.count);
104
+ vh.viewComment.setText(cellDataItem.comment);
105
+ convertView.setBackgroundColor(cellDataItem.check ? Color.GREEN : ROW_BG_COLORS[position%2]);
106
+
107
+ return convertView;
108
+ }
109
+
110
+ // Viewを保持するクラス
111
+ private class ViewHolder {
112
+ final CheckBox viewCheck;
113
+ final TextView viewTime;
114
+ final TextView viewCount;
115
+ final TextView viewComment;
116
+ ViewHolder(View itemView) {
117
+ viewCheck = itemView.findViewById(R.id.listCheckBox);
118
+ viewTime = itemView.findViewById(R.id.listTime);
119
+ viewCount = itemView.findViewById(R.id.listCount);
120
+ viewComment = itemView.findViewById(R.id.listComment);
121
+ }
122
+ }
123
+ }
124
+
125
+ private class NowTimer extends TimerTask implements DefaultLifecycleObserver {
126
+ private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
127
+ private TextView clockText;
128
+ private Handler handler;
129
+ private Timer timer;
130
+ private long period;
131
+ private Date now;
132
+
133
+ NowTimer(long period) {
134
+ this.period = period;
135
+ clockText = findViewById(R.id.clock_text);
136
+ handler = new Handler(getMainLooper());
137
+ }
138
+
139
+ Date getDate() {
140
+ return now;
141
+ }
142
+
143
+ @Override
144
+ public void run() {
145
+ now = new Date();
146
+ handler.post(() -> clockText.setText(dateFormat.format(now)));
147
+ }
148
+ @Override
149
+ public void onStart(@NonNull LifecycleOwner owner) {
150
+ DefaultLifecycleObserver.super.onStart(owner);
151
+ timer = new Timer();
152
+ timer.schedule(this, 0, period);
153
+ }
154
+ @Override
155
+ public void onStop(@NonNull LifecycleOwner owner) {
156
+ DefaultLifecycleObserver.super.onStop(owner);
157
+ timer.cancel();
158
+ timer = null;
159
+ }
160
+ }
161
+
162
+ private class Counter {
163
+ private DecimalFormat df = new DecimalFormat("#,###");
164
+ private TextView countText;
165
+ private int count;
166
+
167
+ Counter(int min, int max) {
168
+ countText = findViewById(R.id.count_text);
169
+ setCount(min);
170
+
171
+ Button plusButton = findViewById(R.id.plus_button);
172
+ plusButton.setOnClickListener(v -> setCount(Math.min(count+1, max)));
173
+
174
+ Button minusButton = findViewById(R.id.minus_button);
175
+ minusButton.setOnClickListener(v -> setCount(Math.max(count-1, min)));
176
+ }
177
+
178
+ void setCount(int count) {
179
+ this.count = count;
180
+ countText.setText(df.format(count));
181
+ }
182
+ int getCount() {
183
+ return count;
184
+ }
185
+ }
186
+ }
187
+ ```