当我更改已经可见的 TextView 的文本时,我的 ListView 中的第一项不会更新

The first item in my ListView will not update when I change the text of an already visible TextView

我有一个 ListView,它由我的自定义 BaseAdapter 填充,其中的视图包含几个 TextView 和 ImageButton。如果用户从其中一个列表项的 PopupMenu 中选择一个项目,它会更改该列表项上关联的 TextView 的文本。这对我的 ListView 中的所有项目都非常有用,除了第一个。由于某种原因,第一项从不更新 TextView。我怀疑它与我的 convertView 有关,因为该项目没有被重置,但我不确定如何修复它。

这是我的设置。我有一个由自定义 BaseAdapter 填充的 ListView。在我的 BaseAdapter 中,我的 getView():

public View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null) {
        IMOModGroup modGroup = modGroups.get(position);
        convertView = modGroup.getConvertView(parent);
    } 
    return convertView;
}

IMOModGroup 是一种特殊的数据结构,它包含每个列表项的所有状态和数据。因此,当 convertView 变为 null 时,BaseAdapter 会从 IMOModGroup 为该位置请求一个新的 convertView。这是创建新的 convertView 的 IMOModGroup 方法:

public View getConvertView(ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView = inflater.inflate(R.layout.modifier_list_item, parent, false);

    //...

    TextView selectedModText = (TextView) convertView.findViewById(R.id.modName);
    selectedModText.setText(R.string.no_modifier_selected);
    selectedModText.setTextColor(context.getResources().getColor(R.color.grayed_out_text_color));

    //...

    return convertView;
}

用户可以在每个列表项上打开一个 PopupMenu 并进行选择。当他们这样做时,上面代码中的 TextView 被更改以反映新文本的选择。该代码如下所示:

public boolean onMenuItemClick(MenuItem item) {
    String selectedMod = item.getTitle().toString();

    TextView selectedModText = (TextView) convertView.findViewById(R.id.modName);
    selectedModText.setText(selectedMod);
    selectedModText.setTextColor(context.getResources().getColor(R.color.imo_blue_light));

    //...

    return false;
}

除第一个项目外,所有这些都适用于 ListView 中的每个项目,它从不更新以反映 TextView 的变化。我已经过测试以确保 TextView 文本确实发生了变化,并且可以在以后调用和查看,并且设置正确。所以看起来数据是正确的,但视图没有得到更新。

我很确定问题是我的 convertView 没有被重置,所以 ListView 只是继续使用带有旧文本的旧视图。设置文本后,我尝试在 BaseAdapter 上调用 notifyDataSetChanged(),但这没有用。

这个问题看起来很有希望:First item in list view is not displaying correctly

但我不确定这是否是我的问题,而且我不确定如何将其应用到我的场景中。

有什么想法吗?我已经阅读了一些关于 SO 和 Google 的类似问题,但其中 none 似乎确实与我的特定场景相关。我使用 ListViews 和 Adapters 已经有一段时间了,这是我第一次看到这样的东西。感谢您的帮助。


根据评论进行编辑

这是完整的 BaseAdapter 代码...

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.util.ArrayList;

public class IMOModListAdapter extends BaseAdapter {

    private Context context;
    private ArrayList<IMOModGroup> modGroups;

    public IMOModListAdapter(Context context, ArrayList<IMOModGroup> modGroups) {
        this.context = context;
        this.modGroups = modGroups;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            IMOModGroup modGroup = modGroups.get(position);
            convertView = modGroup.getConvertView(parent);
        }
        return convertView;
    }

    public Object getItem(int position) {
        return 0;
    }

    public long getItemId(int position) {
        return 0;
    }

    public int getCount() {
        return modGroups.size();
    }
}

进一步测试

随着我深入调试,我意识到当列表中元素 1 的 TextView 发生变化时,甚至根本没有调用 getView()。但是当任何其他元素发生变化时, getView() 将被正常调用。

作为完整性检查,我尝试从外部更改第一个列表项的文本,从父 Activity 本身。如果我尝试更改第一项,它什么都不做,但如果我尝试更改任何其他列表项,它工作正常!这太奇怪了!这是我试过的:

//Set the text of the first item. This does NOTHING and the TextView
//in the item remains unchanged
IMOModGroup modGroup = modGroups.get(0);
modGroup.setTestText("This is some test text");
modList.notifyDataSetChanged();

//Set the text of the second item. This works completely fine!!!!!
IMOModGroup modGroup = modGroups.get(1);
modGroup.setTestText("This is some test text");
modList.notifyDataSetChanged();

嗯,我找到了解决这个问题的方法。只是研究关于 BaseAdapters 和 getView() 的随机 SO 问题,我终于遇到了 this post by Romain Guy,他在其中声明你永远不应该将 wrap_content 用于 ListView 的 layout_height,因为它导致 ListView 多次调用适配器的 getView() 只是为了创建用于测量内容高度的虚拟视图。原来这是导致我的问题的原因,所以将我的 layout_height 更改为 match_parent 就成功了。

不幸的是,现在这导致了一个新问题,因为我无法使用 match_parent,因为它会隐藏我父布局中的其他视图。但至少第一个列表项现在正在正确更新。


更新:

我了解了如何在填充适配器后手动和动态设置 ListView 的高度。我使用了下面的方法,希望这能帮助 运行 遇到类似问题的人。这样我就不必使用 wrap_content 但我仍然可以将 ListView 高度设置为与其内容一样大。

private void setModListHeight() {
    int numberOfItems = modGroups.size(); //the ArrayList providing data for my ListView
    int totalItemsHeight = 0;

    for(int position = 0; position < numberOfItems; position++) {
        View item = modListAdapter.getView(position, null, modList);
        item.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        totalItemsHeight += item.getMeasuredHeight();
    }

    int totalDividersHeight = modList.getDividerHeight() * (numberOfItems - 1);

    ViewGroup.LayoutParams params = modList.getLayoutParams();
    params.height = totalItemsHeight + totalDividersHeight;
    modList.setLayoutParams(params);
    modList.requestLayout();
}