Search code examples
androidperformancelistadapter

Custom list array adapter designed for performance : where to add listeners?


I've written a custom listview array adapter, with the recommended pattern that says that we should use a ViewHolder inside getView() method in order to gain performance (and battery ?). Where I am in trouble is where should I add listeners to my checkboxes components. I will be more explicit after my listview adapter source file :

package com.loloof64.android.chess_position_manager.file_explorer;

// all my importes (eluded)

public class FilesListArrayAdapter extends ArrayAdapter<ListFileElement> {

    private final Context context;
    private final ArrayList<ListFileElement> elements;
    private final ArrayList<ListFileElement> selectedElements = new ArrayList<>();
    private boolean isSelectionMode = false;

    public FilesListArrayAdapter(Context context, ListFileElement[] objects) {
        super(context, R.layout.file_list_view, objects);
        this.context = context;
        this.elements = new ArrayList<>(Arrays.asList(objects));
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;

        // reuse views
        if (rowView == null){
            LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(file_list_view, parent, false);

            // configure view holder
            FileItemViewHolder viewHolder = new FileItemViewHolder();
            viewHolder.checkBoxView = (CheckBox) rowView.findViewById(R.id.file_list_item_checkbox);
            viewHolder.nameView = (TextView) rowView.findViewById(R.id.file_list_item_name);
            viewHolder.iconView = (ImageView) rowView.findViewById(R.id.file_list_item_icon);

            // **** 1 *****
            // Should I add item listeners here ?

            rowView.setTag(viewHolder);
        }

        // fill data
        FileItemViewHolder viewHolder = (FileItemViewHolder) rowView.getTag();
        ListFileElement bindedElement = elements.get(position);
        viewHolder.nameView.setText(bindedElement.getFileName());

        // **** 2 *****
        // Should I rather add item listeners here ?

        viewHolder.checkBoxView.setVisibility(isSelectionMode ? View.VISIBLE : View.GONE);
        viewHolder.checkBoxView.setClickable(ListFileElement.PARENT_DIR != bindedElement);

        if (bindedElement.isDirectory()){
            viewHolder.iconView.setImageResource(R.drawable.ic_folder);
        }
        else {
            viewHolder.iconView.setImageResource(R.drawable.ic_file);
        }

        return rowView;
    }

    public boolean isSelectionMode() {
        return isSelectionMode;
    }

    public void toggleSelectionMode(){
        isSelectionMode = !isSelectionMode;
    }

    public ListFileElement[] getSelectedElements(){
        return selectedElements.toArray(new ListFileElement[0]);
    }
}

Of course, ListFileElement source code is not needed in order to understand my problem. You should have noticed sections **** 1 ***** and **** 2 **** in code comment of my source file.

What makes me into trouble, is the item loading efficiency and programm correctness (no bug) : should I add items checkbox listeners when adding control inside ViewHolder (section 1) or after each item view loading (section 2) ?


Solution

  • You can set your listeners once, when creating the rows. However, setting them once you need some way of knowing (inside the listener) what was clicked. To do this you'll have to specify the element that the row belongs to when binding the view. Something similar to this should work:

    // reuse views
    if (rowView == null){
        ...
        // **** 1 *****
        viewHolder.checkboxView.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 Checkbox cb = (Checkbox) v;
                 int position = (Integer)v.getTag();
                 ListFileElement bindedElement = getItem(position);
                 if (cb.isChecked()) {
                     // Do something
                 } else {
                     // Do something else
                 }
             }
         });
        rowView.setTag(viewHolder);
    }
    
    // Bind the view
    viewHolder.checkboxView.setTag(position);
    

    Since you set the position of the item as the tag on the checkbox when binding the view, the checkbox click listener can simply grab that position out of the checkbox tag and acquire a reference to the item.

    Alternatively, you could simply set the item as the tag, rather than a position reference!