Android实现CustomListAdapter时无法使用List<String>及多态列表布局问题
嘿,我来帮你搞定这两个Android ListAdapter的问题,都是日常开发里很常见的场景,一步步来哈~
其实List<String>完全可以作为Adapter的数据源,大概率是你在自定义Adapter时的泛型处理或者数据绑定环节出了小疏漏。给你一个最基础的可运行示例:
首先创建继承自BaseAdapter的自定义Adapter:
public class StringListAdapter extends BaseAdapter { private Context mContext; private List<String> mDataList; public StringListAdapter(Context context, List<String> dataList) { mContext = context; mDataList = dataList; } @Override public int getCount() { return mDataList.size(); } @Override public Object getItem(int position) { return mDataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_string, parent, false); holder = new ViewHolder(); holder.tvContent = convertView.findViewById(R.id.tv_content); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvContent.setText(mDataList.get(position)); return convertView; } static class ViewHolder { TextView tvContent; } }
然后在Activity中初始化并绑定:
List<String> stringList = Arrays.asList("爬行动物", "猫科动物", "灵长类"); ListView listView = findViewById(R.id.list_view); StringListAdapter adapter = new StringListAdapter(this, stringList); listView.setAdapter(adapter);
注意:如果觉得自定义BaseAdapter太繁琐,直接用系统的ArrayAdapter<String>会更简单——new ArrayAdapter<>(this, R.layout.item_string, stringList)就能搞定,前提是你布局里的TextView ID和Adapter默认绑定的ID匹配(或者你可以重载构造方法指定ID)。如果之前报错,优先检查布局文件的控件ID是否正确、数据源是否为空这些细节。
因为你现在只需要展示分组项(而非具体动物),我们可以先定义分组类型标识,然后让Adapter根据不同分组加载对应的布局,同时保留未来扩展的灵活性。
步骤1:定义分组类型
先创建一个枚举来标识所有动物分组,方便统一管理:
public enum AnimalGroup { REPTILE("爬行动物"), FELINE("猫科动物"), PRIMATE("灵长类"); private String groupName; AnimalGroup(String groupName) { this.groupName = groupName; } public String getGroupName() { return groupName; } }
步骤2:创建对应布局
为每个分组单独写布局文件,比如:
item_reptile_group.xml:爬行动物分组的专属布局(比如背景绿色、文字加粗)item_feline_group.xml:猫科动物分组布局(背景橙色)item_primate_group.xml:灵长类分组布局(背景蓝色)
每个布局里至少要有一个TextView用来显示分组名称,ID统一设为tv_group_name即可。
步骤3:实现支持多布局的Adapter
核心是重写getViewTypeCount()和getItemViewType()方法,告诉系统我们有多种布局类型,然后在getView()里根据类型加载对应布局:
public class AnimalGroupAdapter extends BaseAdapter { private Context mContext; private List<AnimalGroup> mGroupList; // 布局类型常量 private static final int TYPE_REPTILE = 0; private static final int TYPE_FELINE = 1; private static final int TYPE_PRIMATE = 2; public AnimalGroupAdapter(Context context, List<AnimalGroup> groupList) { mContext = context; mGroupList = groupList; } @Override public int getCount() { return mGroupList.size(); } @Override public Object getItem(int position) { return mGroupList.get(position); } @Override public long getItemId(int position) { return position; } // 返回布局类型的总数 @Override public int getViewTypeCount() { return 3; } // 返回当前Item对应的布局类型 @Override public int getItemViewType(int position) { AnimalGroup group = mGroupList.get(position); switch (group) { case REPTILE: return TYPE_REPTILE; case FELINE: return TYPE_FELINE; case PRIMATE: return TYPE_PRIMATE; default: return TYPE_REPTILE; } } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; int viewType = getItemViewType(position); AnimalGroup group = mGroupList.get(position); if (convertView == null) { // 根据类型加载对应的布局 switch (viewType) { case TYPE_REPTILE: convertView = LayoutInflater.from(mContext).inflate(R.layout.item_reptile_group, parent, false); break; case TYPE_FELINE: convertView = LayoutInflater.from(mContext).inflate(R.layout.item_feline_group, parent, false); break; case TYPE_PRIMATE: convertView = LayoutInflater.from(mContext).inflate(R.layout.item_primate_group, parent, false); break; } holder = new ViewHolder(); holder.tvGroupName = convertView.findViewById(R.id.tv_group_name); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvGroupName.setText(group.getGroupName()); return convertView; } static class ViewHolder { TextView tvGroupName; } }
步骤4:在Activity中初始化
List<AnimalGroup> groupList = Arrays.asList(AnimalGroup.REPTILE, AnimalGroup.FELINE, AnimalGroup.PRIMATE); ListView groupListView = findViewById(R.id.group_list_view); AnimalGroupAdapter adapter = new AnimalGroupAdapter(this, groupList); groupListView.setAdapter(adapter);
扩展:基于实际动物对象生成分组
如果之后需要从你的Animal多态集合里动态生成分组(比如只展示存在的动物类型),可以先做分组统计:
// 假设你有List<Animal> allAnimals数据源 Map<Class<? extends Animal>, List<Animal>> animalGroups = new HashMap<>(); for (Animal animal : allAnimals) { Class<? extends Animal> type = animal.getClass(); animalGroups.computeIfAbsent(type, k -> new ArrayList<>()).add(animal); } // 把统计后的类型转换成AnimalGroup枚举列表 List<AnimalGroup> groupList = new ArrayList<>(); for (Class<? extends Animal> type : animalGroups.keySet()) { if (Reptile.class.isAssignableFrom(type)) { groupList.add(AnimalGroup.REPTILE); } else if (Feline.class.isAssignableFrom(type)) { groupList.add(AnimalGroup.FELINE); } else if (Primate.class.isAssignableFrom(type)) { groupList.add(AnimalGroup.PRIMATE); } // 新增动物类型时,在这里加对应的判断即可 }
这样就能动态生成实际存在的动物分组,完全适配你的多态层级结构。
内容的提问来源于stack exchange,提问作者Chris V.

