Android新增CardView触发NullPointerException异常修复
问题描述
开发时间表应用时,点击add按钮向存储行对象的ArrayList<row> rows集合新增CardView行时应用崩溃,报错为java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.matches(java.lang.String)' on a null object reference,即使上一行已经填写内容也无法新增。
每个行布局包含1个展示序号的TextView、2个分别填写任务名、工时的EditText,原有逻辑会校验上一行内容非空才允许新增。
崩溃根因
row类构造函数逻辑无效:构造方法内仅声明了局部变量接收成员变量默认值,没有给成员变量赋初始值。新建row对象时,String类型的tasksName默认值为null,int类型的hours默认值为0,直接对null调用matches()方法直接触发空指针。- 数据没有双向绑定:现有Adapter仅实现了将
row对象的数据设置到控件上的逻辑,没有监听EditText的输入事件将用户填写的内容同步回rows集合中的row对象,即使用户填完内容,row实例里的tasksName还是初始的null、hours还是0,校验逻辑永远无法拿到用户输入的真实值。 - 校验逻辑存在缺陷:原有代码用
matches(" ")仅能匹配单个空格的字符串,无法正确识别空字符串、全空格等无效输入场景。 - 列表刷新位置错误:新增行后调用
notifyItemInserted(rows.size())传入的索引越界,列表长度为n时元素索引范围是0~n-1,新插入元素的位置应为rows.size()-1。
修复方案
1. 修正row类定义
遵循Java类名大驼峰命名规范将类名改为Row,修正构造函数给成员变量赋合理默认值:
package com.example.timetable; public class Row { int index; int hours; String tasksName; public Row() { // 给成员变量赋默认值,避免空指针 this.index = 0; this.hours = 0; this.tasksName = ""; } public int getHours() { return hours; } public int getIndex() { return index; } public String getTasksName() { return tasksName; } public void setIndex(int index) { this.index = index; } public void setHours(int hours) { this.hours = hours; } public void setTasksName(String tasksName) { this.tasksName = tasksName; } }
注意:全局替换所有原row类引用为Row,包括ArrayList<Row> rows、Adapter的集合定义等位置。
2. 给Adapter增加输入同步逻辑
在Adapter中给EditText添加文本变化监听,用户输入时实时同步内容到对应的数据对象,注意先移除旧监听避免绑定数据时触发错乱:
// 顶部新增导入 import android.text.Editable; import android.text.TextWatcher; // 修正onBindViewHolder方法 @Override public void onBindViewHolder(@NonNull cellVH holder, int position) { Row currentRow = unitCell.get(position); // 序号直接用位置+1生成,无需单独存储 holder.index.setText(String.valueOf(position + 1)); // 移除旧的文本监听,避免复用 ViewHolder 时数据错乱 if (holder.tasksName.getTag() instanceof TextWatcher) { holder.tasksName.removeTextChangedListener((TextWatcher) holder.tasksName.getTag()); } if (holder.totalHours.getTag() instanceof TextWatcher) { holder.totalHours.removeTextChangedListener((TextWatcher) holder.totalHours.getTag()); } // 把控件当前值设置为数据对象存储的值 holder.tasksName.setText(currentRow.getTasksName()); holder.totalHours.setText(currentRow.getHours() == 0 ? "" : String.valueOf(currentRow.getHours())); // 监听任务名输入,实时同步到数据对象 TextWatcher taskNameWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { currentRow.setTasksName(s.toString().trim()); } @Override public void afterTextChanged(Editable s) {} }; holder.tasksName.addTextChangedListener(taskNameWatcher); holder.tasksName.setTag(taskNameWatcher); // 监听工时输入,实时同步到数据对象,处理非数字输入异常 TextWatcher hoursWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String input = s.toString().trim(); try { currentRow.setHours(input.isEmpty() ? 0 : Integer.parseInt(input)); } catch (NumberFormatException e) { currentRow.setHours(0); } } @Override public void afterTextChanged(Editable s) {} }; holder.totalHours.addTextChangedListener(hoursWatcher); holder.totalHours.setTag(hoursWatcher); }
3. 修正新增按钮的校验与刷新逻辑
增加空判断,修正校验规则,修正列表刷新的索引位置:
addBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Row lastRow = rows.get(rows.size() - 1); String lastTaskName = lastRow.getTasksName(); int lastHours = lastRow.getHours(); // 校验上一行是否填写完整 if (lastTaskName == null || lastTaskName.isEmpty() || lastHours <= 0) { Toast.makeText(TimeTable.this, "Fill out previous row's content", Toast.LENGTH_SHORT).show(); return; } // 新增行 Row newRow = new Row(); newRow.setIndex(rows.size()); rows.add(newRow); // 传入正确的插入位置索引 mAdapter.notifyItemInserted(rows.size() - 1); // 自动滚动到最新添加的行 recyclerView.smoothScrollToPosition(rows.size() - 1); } });
内容的提问来源于stack exchange,提问作者Parth Gupta
相关产品推荐
相关产品推荐

