dynamically inflating views into listView items. odd behaviour

1k views Asked by At

So I'm dynamically creating views based on how many I need into a listView item. When the user clicks on a listView item I expand the item to display the inflated view(s) I made. The reason I have to do it this way is because the amount of inflated views needs to be dynamic. There could be 2, 3, or even 5+.

the inflation happens fine, the view expands great. The problem is when I scroll in my listView. It seems to inflate views to other listview items instead of the one the user clicked on. I know this is intended behavior as listViews reuse their views to conserve memory, but is there a reason why it's doing that in my code?

To give some backstory, this class is a custom view that I put in another xml file as an element of a RelativeLayout. the setLayout function should inflate as many views as necessary. (This is for polling functionality) And my adapter is most likely where the problem is.

public class SurveyView extends LinearLayout {
private LinearLayout pollContainer;
private Context context;
private String type;
private int numOfAnswers;
private ListView answersList;
private ArrayList<String> answers;
private boolean visibility = true;
private OnClickListener listener;
private ArrayList<View> options;
private int tag = 888888888;

/**
 *
 * @param context the context of the activity
 * @param type the type of poll
 * @param numOfAnswers if the poll is multiple choice (most likely) provide number of answers.
 */
public void setLayout(Context context, String type, int numOfAnswers) {
    this.type = type;
    this.numOfAnswers = numOfAnswers;
    this.context = context;

    switch (type) {
        case "Multiple":
            if (visibility) {
                for (int i = 0; i < numOfAnswers; i++) {
                    View v = LayoutInflater.from(getContext()).inflate(R.layout.poll_multiple_choice_answers_row, null);
                    v.setTag(tag);
                    tag++;
                    v.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Toast.makeText(getContext(), v.getTag().toString(), Toast.LENGTH_SHORT).show();
                            RadioButton rb = (RadioButton)v.findViewById(R.id.answer_voted_button);
                            rb.setChecked(true);
                        }
                    });
                    addView(v);
                    options.add(v);
                }
            }
            break;
        case "Slider":
            break;
        case "Tree":
            break;
        case "Sentiment":
            break;
    }
}

public SurveyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(VERTICAL);
    options = new ArrayList<>();
}

Here is my adapter code.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder viewHolder;
    if (convertView == null) {
        viewHolder = new ViewHolder();
        convertView = inflater.inflate(R.layout.polls_card_layout, null);
        viewHolder.type = (TextView)convertView.findViewById(R.id.card_type);
        viewHolder.time = (TextView)convertView.findViewById(R.id.card_poll_time);
        viewHolder.text = (TextView)convertView.findViewById(R.id.card_text);
        viewHolder.space = (TextView)convertView.findViewById(R.id.card_space);
        viewHolder.pollSpace = (TextView)convertView.findViewById(R.id.poll_space);
        viewHolder.type_icon = (ImageView)convertView.findViewById(R.id.card_icon);
        viewHolder.answerView = (SurveyView)convertView.findViewById(R.id.poll_component);
        convertView.setTag(viewHolder);
    }
    else {
        viewHolder = (ViewHolder)convertView.getTag();
    }

    viewHolder.type.setText(data.get(position).getType());
    viewHolder.time.setText(data.get(position).getTime());
    viewHolder.text.setText(data.get(position).getText());

    convertView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (viewHolder.getAnswerView().getHeight() == 0) {
                viewHolder.answerView.setLayout(context, "Multiple", 5);
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                params.addRule(RelativeLayout.BELOW, R.id.poll_space);
                params.addRule(RelativeLayout.RIGHT_OF, R.id.card_icon);
                viewHolder.answerView.setLayoutParams(params);
            } else {
                viewHolder.getAnswerView().setVisibility(false);
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, 0);
                params.addRule(RelativeLayout.BELOW, R.id.poll_space);
                params.addRule(RelativeLayout.RIGHT_OF, R.id.card_icon);
                viewHolder.answerView.setLayoutParams(params);
            }
        }
    });
    return convertView;
}
1

There are 1 answers

5
MiltoxBeyond On BEST ANSWER

You should be applying your onClick logic in a method of the listview, since it has the OnItemClickListener/OnItemSelectedListener. Since elements are recycled your onclick may apply to the actual element not the relative item to the list.

A complete example of how to use the ListView is available here: https://developer.android.com/guide/topics/ui/layout/listview.html

But the basic idea is you extend the OnItemClick method which receives the list, view selected (actual view in use), and the id of the item. With the list you can get the data stored and any related data.

However you should look into using the RecyclerView to create lists. Same idea applies but it is more efficient for displaying lists and does a lot of the work for you.