I'm using an ArrayAdapter for a list of my own type of objects (only one type) and I give the user an option to create more items (thus creating more views for those items). At some point, getView sent a new "position" index with a non-null "convertView". It then shows the first view in the last position. After that, when scrolling the views get all mixed up. I'm assuming this means I manipulated the views in ways I shouldn't have but I just don't see where. Here is some code:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
PreviewItemHolder holder = null;
// Initialize view if convertview is null
if (convertView == null) {
v = newView(parent, position);
}
// Populate from previously saved holder
else {
// Use previous item if not null
v = convertView;
}
// Populate if the holder is null (newly inflated view) OR
// if current view's holder's flag is true and requires populating
if ((holder == null) || (holder.readPopulateFlag())) {
bindView(position, v);
}
return v;
}
private View newView(ViewGroup parent, int position) {
// Getting view somehow...
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View inflatedView = inflater.inflate(R.layout.preview_element_set, parent, false);
PreviewItemHolder holder = new PreviewItemHolder();
holder.set = (Set) mSets.get(position);
holder.previewElementHolders = new ArrayList<PreviewElementHolder>();
holder.expandArea = (View) inflatedView.findViewById(R.id.expandArea);
holder.repetitionsLabel = (TextView) inflatedView.findViewById(R.id.previewRepetitionsInput);
holder.endlessInput = (CheckBox) inflatedView.findViewById(R.id.previewSetEndlessInput);
holder.nameLabel = (TextView) inflatedView.findViewById(R.id.previewSetNameLabel);
holder.commentInput = (EditText) inflatedView.findViewById(R.id.previewSetCommentInput);
holder.soundInput = (EditText) inflatedView.findViewById(R.id.previewSetSoundInput);
holder.addElementButton = (Button) inflatedView.findViewById(R.id.previewSetAddElements);
holder.expand = (View) inflatedView.findViewById(R.id.infoArea);
holder.collapse = (View) inflatedView.findViewById(R.id.collapse);
final int setsLength = holder.set.getElements().size();
for (int i = 0; i < setsLength; i++) {
AElement currElement = holder.set.getElements().get(i);
// Creating new element holder according to the type
if (currElement instanceof Rest) {
holder.previewElementHolders.add(new PreviewRestHolder());
}
else if (currElement instanceof TimeExercise) {
holder.previewElementHolders.add(new PreviewTimeExerciseHolder());
}
else if (currElement instanceof RepetitionExercise) {
holder.previewElementHolders.add(new PreviewRepetitionExerciseHolder());
}
View currLayout = inflateElement(currElement, inflater, i, holder.previewElementHolders.get(i));
// Add the child before the hairline, collapse image and the add
// button
// (3 last children of the expandArea view
((ViewGroup) holder.expandArea).addView(currLayout, ((ViewGroup) holder.expandArea).getChildCount() - CHILDREN_INDEX_AFTER_PHASES_LABEL);
}
inflatedView.setTag(holder);
return inflatedView;
}
private void bindView(int position, View inflatedView) {
final PreviewItemHolder holder = (PreviewItemHolder) inflatedView.getTag();
holder.set.setId(position);
holder.endlessInput.setChecked(holder.set.getEndless());
holder.soundInput.setText(holder.set.getSound());
holder.nameLabel.setText(holder.set.getName());
holder.commentInput.setText(holder.set.getComment());
// Make sure there is a name. If none, put default
if (holder.nameLabel.getText().equals("")) {
holder.nameLabel.setText(R.string.default_set_name);
}
// Set repetitions value according to the endless flag
if (holder.set.getEndless()) {
holder.repetitionsLabel.setText(R.string.infinity);
}
else {
holder.repetitionsLabel.setText(String.valueOf(holder.set.getRepetitions()));
}
// Set click listeners
holder.endlessInput.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Save endless flag
holder.set.setEndless(isChecked);
// If an endless set - Dropset
if (isChecked) {
holder.repetitionsLabel.setText(R.string.infinity);
}
else {
// Regular set
holder.repetitionsLabel.setText(String.valueOf(holder.set.getRepetitions()));
}
hideShowRepsWeights(holder);
}
});
holder.repetitionsLabel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
NumericDialog instance = NumericDialog.newInstance(holder, holder.set, NumericDialog.INTEGER_MODE, Consts.SET_REPETITIONS_METHOD_NAME);
instance.show(((Activity) getContext()).getFragmentManager(), null);
}
});
holder.nameLabel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Setting flag to true to allow populating this view
holder.rePopulateFlag = true;
SetNameDialog instance = SetNameDialog.newInstance(holder.set);
instance.show(((Activity) getContext()).getFragmentManager(), null);
}
});
holder.commentInput.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
// After focus is lost, save the text into the set
holder.set.setComment(holder.commentInput.getText().toString());
}
}
});
// TODO Change that into a dialog that allows selection of sounds
holder.soundInput.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
// After focus is lost, save the text into the set
holder.set.setSound(holder.soundInput.getText().toString());
}
}
});
holder.expand.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Change visibility - Show expandArea and its data
holder.expandArea.setVisibility(View.VISIBLE);
holder.expand.setVisibility(View.GONE);
holder.collapse.setVisibility(View.VISIBLE);
}
});
holder.collapse.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Change visibility - Hide expandArea and its data
holder.expandArea.setVisibility(View.GONE);
holder.collapse.setVisibility(View.GONE);
holder.expand.setVisibility(View.VISIBLE);
}
});
holder.addElementButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
AddElementDialog instance = AddElementDialog.newInstance(holder);
instance.show(((Activity) getContext()).getFragmentManager(), null);
}
});
// Populate elements
for (PreviewElementHolder elementHolder : holder.previewElementHolders) {
populateElement(elementHolder, holder);
}
// Finally hide/show if needed - Should this be put somewere else?
hideShowRepsWeights(holder);
}
Please tell me if you think I should upload more methods to make things clearer.
A friend explained the problem to me and now it seems to work. Basically ListView only holds a small number of views and recycles them all the time. In my case I have a Nexus 4 and so it seems to have 7 views total because the 8th was always the one who started to cause trouble. What I was missing in my getView() was a condition checking for correlation between the position and the ID of the current item within the ArrayAdapter. Here is how it looks now that it works: