ListView 項目同時包含 TextView 與 CheckBox 時,直接在 Activity 中 findViewById 獲取 CheckBox 註冊監聽會導致 NullPointerException,原因:ListView 尚未佈局完成,項目視圖不存在。
// 錯誤:在 Activity onCreate 中直接查找 ListView 子視圖
CheckBox cb = (CheckBox) listview.findViewById(R.id.checkBox12);
cb.setOnCheckedChangeListener(...); // NPE
在 getView() 中為每個項目的 CheckBox 獨立註冊監聽:
public class TaskAdapter extends BaseAdapter {
private Context context;
private List<Task> tasks;
private OnCheckChangeListener listener;
public interface OnCheckChangeListener {
void onCheckChanged(int position, boolean isChecked);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_task, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Task task = tasks.get(position);
holder.bind(task, position);
return convertView;
}
static class ViewHolder {
TextView tvTitle;
CheckBox cb;
ViewHolder(View view) {
tvTitle = view.findViewById(R.id.tv_title);
cb = view.findViewById(R.id.cb);
}
void bind(Task task, int position) {
tvTitle.setText(task.title);
// 關鍵:先移除舊監聽,防止複用錯亂
cb.setOnCheckedChangeListener(null);
cb.setChecked(task.checked);
cb.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (listener != null) {
listener.onCheckChanged(position, isChecked);
}
task.checked = isChecked; // 同步更新資料源
});
}
}
}
findViewById,提升滾動流暢度positionsetOnCheckedChangeListener(null):清除複用視圖殘留監聽,防止多重回調task.checked = isChecked 保證滾動後狀態不丟失新專案建議使用 RecyclerView 搭配 ListAdapter 與 DiffUtil,自動處理動畫與局部刷新,寫法更簡潔:
// ViewHolder 內
cb.setOnCheckedChangeListener((btn, checked) -> {
int pos = getAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
listener.onCheckChanged(pos, checked);
}
});