Search code examples
androidcustom-adapter

Android ExpandableListView with CustomAdapter display artifacts


I am facing a bug with ExpandableListView and a Custom Adapter It looks like TextView takes a not own value, you can see on video. Model doesn't change this value only in this list

Name and phone don't affect, but two right textview is.

https://www.youtube.com/embed/YjC_QsA75yo

Has somebody met this bug? any idea how to trace it and fix?

upd

public ExpandableAdapter(List<CategoryModel> categoryModels, Context ctx) {
    this.categoryModels = categoryModels;
    this.ctx = ctx;
}

@Override
public int getGroupCount() {
    return categoryModels.size();
}

@Override
public int getChildrenCount(int groupPosition) {
    return categoryModels.get(groupPosition).getMembers().size();
}

@Override
public Object getGroup(int groupPosition) {
    return categoryModels.get(groupPosition);
}

@Override
public Object getChild(int groupPosition, int childPosition) {
    return categoryModels.get(groupPosition).getMembers().get(childPosition);
}

@Override
public long getGroupId(int groupPosition) {
    return categoryModels.get(groupPosition).getId();
}

@Override
public long getChildId(int groupPosition, int childPosition) {
    return categoryModels.get(groupPosition).getMembers().get(childPosition).getId();
}

@Override
public boolean hasStableIds() {
    return true;
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

    View v = convertView;
    if (v == null) {
        LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
                (Context.LAYOUT_INFLATER_SERVICE);
        v = inflater.inflate(R.layout.reg_group_layout, parent, false);
    }

    TextView groupName = (TextView) v.findViewById(R.id.tv_group_name);
    TextView groupCount = (TextView) v.findViewById(R.id.tv_group_count);

    CategoryModel cat = categoryModels.get(groupPosition);
    groupName.setText(cat.toString());
    groupCount.setText(cat.getMembers().size() + "");

    return v;
}

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

    View v = convertView;

    if (v == null) {
        LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
                (Context.LAYOUT_INFLATER_SERVICE);
        v = inflater.inflate(R.layout.reg_member_layout, parent, false);
    }

    TextView tvName = (TextView) v.findViewById(R.id.tv_reg_mem_name);
    TextView tvPhone = (TextView) v.findViewById(R.id.tv_reg_mem_phone);
    TextView tvFinishTime = (TextView) v.findViewById(R.id.tv_reg_finishtime);
    TextView tvPenaltyTime = (TextView) v.findViewById(R.id.tv_reg_penaltytime);

    MemberModel mem = (MemberModel) getChild(groupPosition, childPosition);

    if (!mem.getActive()) {
        tvName.setTextColor(Color.GRAY);
        tvPhone.setTextColor(Color.GRAY);
    } else {
        tvName.setTextColor(Color.BLACK);
        tvPhone.setTextColor(Color.BLACK);
    }

    tvName.setText(mem.getMem_name());
    tvPhone.setText(mem.getMem_tel());
    Util.setFinishTime(tvFinishTime, mem.getFinishTime());
    Long penTime = mem.getPenaltyTime();
    if (penTime != null && penTime != 0) {
        Util.setPenaltyTime(tvPenaltyTime, penTime);
    } else {
        //tvPenaltyTime.setVisibility(View.INVISIBLE);
    }
    return v;
}


@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true;
}

@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
    return true;
}

@Override
public void registerDataSetObserver(DataSetObserver observer) {
    super.registerDataSetObserver(observer);
}

Solution

  • Make sure you always set the FinishTime and Penaltytime, even to spaces if empty.

    What is happening, you're given convertViews (old views with old content) and you need to fill them up with new content. These lines:

      if (penTime != null && penTime != 0) {
            Util.setPenaltyTime(tvPenaltyTime, penTime);
        } else {
            //tvPenaltyTime.setVisibility(View.INVISIBLE);
        }
    

    will set the penalty time if it's not 0, but if it is 0, you're leaving the old value of the old view, whichever it was. That's why when a row with penalty time exits your screen, the new view that enters the screen will keep the old penalty time if it doesn't have one.

    Pays off to learn how View Recycling works! You can further optimize your adapter by using a ViewHolder and caching findViewById's.

    So you could do:

        if (penTime != null && penTime != 0) {
            Util.setPenaltyTime(tvPenaltyTime, penTime);
        } else {
            Util.setPenaltyTime(tvPenaltyTime, 0);
        }
    

    or, as looks like you attempted,

        if (penTime != null && penTime != 0) {
            Util.setPenaltyTime(tvPenaltyTime, penTime);
            tvPenaltyTime.setVisibility(View.VISIBLE);
        } else {
            tvPenaltyTime.setVisibility(View.INVISIBLE);
        }