带EditText子项的ExpandableListView的Adapter实现技术问询
带EditText的ExpandableListView Adapter完整实现指南
嗨,我来帮你搞定这个带EditText子项的ExpandableListView问题!结合你的代码片段,我会给出完整的Adapter实现,同时解决EditText数据保存和视图复用的核心痛点。
一、先定义数据模型
首先要让每个子项的EditText数据和对应的模型绑定,这样不管视图怎么复用,数据都不会错乱。我们定义两个模型类:
// 组项模型 public class DeviceGroup { private String groupName; private List<Device> deviceList; public DeviceGroup(String groupName, List<Device> deviceList) { this.groupName = groupName; this.deviceList = deviceList; } // getter和setter省略 public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public List<Device> getDeviceList() { return deviceList; } public void setDeviceList(List<Device> deviceList) { this.deviceList = deviceList; } } // 子项模型,新增字段保存EditText输入内容 public class Device { private String deviceName; private String code; // 对应hint_code的输入内容 private String password; // 对应hint_pass的输入内容 public Device(String deviceName) { this.deviceName = deviceName; this.code = ""; this.password = ""; } // getter和setter必须要有 public String getDeviceName() { return deviceName; } public void setDeviceName(String deviceName) { this.deviceName = deviceName; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
二、完整的Adapter实现(含ViewHolder+数据绑定)
这里核心是用ViewHolder模式优化视图复用,同时给EditText绑定TextWatcher,实时把输入内容同步到Device模型里:
public class DeviceExpandableAdapter extends BaseExpandableListAdapter { private Context mContext; private List<DeviceGroup> mGroupList; public DeviceExpandableAdapter(Context context, List<DeviceGroup> groupList) { this.mContext = context; this.mGroupList = groupList; } // -------------- 组项相关方法 -------------- @Override public int getGroupCount() { return mGroupList.size(); } @Override public DeviceGroup getGroup(int groupPosition) { return mGroupList.get(groupPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { GroupViewHolder groupViewHolder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.group_list, parent, false); groupViewHolder = new GroupViewHolder(); groupViewHolder.tvGroupName = convertView.findViewById(R.id.tv_group_name); convertView.setTag(groupViewHolder); } else { groupViewHolder = (GroupViewHolder) convertView.getTag(); } // 设置组项名称 groupViewHolder.tvGroupName.setText(getGroup(groupPosition).getGroupName()); return convertView; } // -------------- 子项相关方法 -------------- @Override public int getChildrenCount(int groupPosition) { return getGroup(groupPosition).getDeviceList().size(); } @Override public Device getChild(int groupPosition, int childPosition) { return getGroup(groupPosition).getDeviceList().get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { ChildViewHolder childViewHolder; final Device currentDevice = getChild(groupPosition, childPosition); if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.child_list, parent, false); childViewHolder = new ChildViewHolder(); childViewHolder.hintCode = convertView.findViewById(R.id.hint_code); childViewHolder.hintPass = convertView.findViewById(R.id.hint_pass); // 给EditText设置初始的TextWatcher(后续复用会更新绑定的Device) childViewHolder.codeWatcher = new EditTextWatcher(currentDevice, true); childViewHolder.passWatcher = new EditTextWatcher(currentDevice, false); childViewHolder.hintCode.getEditText().addTextChangedListener(childViewHolder.codeWatcher); childViewHolder.hintPass.getEditText().addTextChangedListener(childViewHolder.passWatcher); convertView.setTag(childViewHolder); } else { childViewHolder = (ChildViewHolder) convertView.getTag(); // 复用视图时,先移除旧的Watcher,避免绑定错误的Device childViewHolder.hintCode.getEditText().removeTextChangedListener(childViewHolder.codeWatcher); childViewHolder.hintPass.getEditText().removeTextChangedListener(childViewHolder.passWatcher); // 更新Watcher绑定的当前Device childViewHolder.codeWatcher.updateDevice(currentDevice); childViewHolder.passWatcher.updateDevice(currentDevice); // 重新添加Watcher childViewHolder.hintCode.getEditText().addTextChangedListener(childViewHolder.codeWatcher); childViewHolder.hintPass.getEditText().addTextChangedListener(childViewHolder.passWatcher); } // 设置EditText内容:先移除Watcher避免触发不必要的回调,设置完再添加回来 childViewHolder.hintCode.getEditText().removeTextChangedListener(childViewHolder.codeWatcher); childViewHolder.hintCode.getEditText().setText(currentDevice.getCode()); childViewHolder.hintCode.getEditText().addTextChangedListener(childViewHolder.codeWatcher); childViewHolder.hintPass.getEditText().removeTextChangedListener(childViewHolder.passWatcher); childViewHolder.hintPass.getEditText().setText(currentDevice.getPassword()); childViewHolder.hintPass.getEditText().addTextChangedListener(childViewHolder.passWatcher); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } @Override public boolean hasStableIds() { return true; } // -------------- ViewHolder定义 -------------- static class GroupViewHolder { TextView tvGroupName; } static class ChildViewHolder { TextInputLayout hintCode; TextInputLayout hintPass; EditTextWatcher codeWatcher; EditTextWatcher passWatcher; } // -------------- 自定义TextWatcher,绑定Device模型 -------------- private static class EditTextWatcher implements TextWatcher { private Device mDevice; private boolean isCode; // 区分是code输入框还是password输入框 public EditTextWatcher(Device device, boolean isCode) { this.mDevice = device; this.isCode = isCode; } // 复用视图时更新绑定的Device public void updateDevice(Device device) { this.mDevice = device; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void afterTextChanged(Editable s) { // 实时把输入内容同步到Device模型 if (isCode) { mDevice.setCode(s.toString()); } else { mDevice.setPassword(s.toString()); } } } }
三、核心要点解析
1. 视图复用的关键处理
- 必须使用ViewHolder:避免每次
getChildView都调用findViewById,同时把TextWatcher和ViewHolder绑定,复用视图时可以直接获取并更新绑定的模型。 - 复用视图时先移除旧的
TextWatcher:防止旧的Watcher还绑定着之前的Device,导致输入内容同步到错误的模型里。
2. EditText数据处理
- 把输入内容直接绑定到
Device模型:不管视图怎么复用,数据都存在模型里,不会丢失或错乱。 - 设置EditText文本时先移除Watcher:避免设置文本时触发
afterTextChanged,导致不必要的模型更新。
3. 额外注意事项
- 如果需要获取所有EditText的输入内容,直接遍历
mGroupList里的Device对象即可,不用从视图中获取。 - 若遇到焦点错乱问题,可以在
getChildView里根据需求设置EditText的焦点状态,比如:// 示例:仅当当前子项是展开的组的第一个子项时获取焦点 if (isExpanded && childPosition == 0) { childViewHolder.hintCode.getEditText().requestFocus(); } else { childViewHolder.hintCode.getEditText().clearFocus(); }
内容的提问来源于stack exchange,提问作者PPG
相关产品推荐
相关产品推荐

