2

I have a custom ListAdapter that displays customer requests, and in each row there are three ImageButtons, allowing you to set a status of Completed, In Process, or Cancel, respectively. The ImageButtons are set to work like radio buttons, where only one can be Activated per row, and each button is tagged with the messageID and the desired status.

As the buttons are Activated/Deactivated, I add/remove them from a globally available Arraylist called AppInfo.messageButtons . I later iterate over the ArrayList to determine which requests are to be acted upon, and what action should be performed. Buttons can be clicked once to select, clicked again to deselect. From the point of view of what ends upin my messageButtons arraylist, this part seems to work fine.

The problem I am having is with ImageButtons getting the Activated status without having been clicked. In fact, for each ImageButton I Activate, every 6th button below it in that same column gets activated, as if they were linked. This is regardless of the number of requests. I have done it with 6 requests, and I have done it with 30.

For example, this chart depicts the results of setting Request 1 "Completed", and Request 3 "Cancel". Only 2 clicks, but 5 items show as Activated. Only the original two clicked end up in my AppInfo.messageButtons arraylist, but if I was to click Request 7 Completed again (to deactivate), it would also deactivate 1 and 13. So besides being confusing, it screws up marking messages.

            Complete   InProcess   Cancel
Request 1      X           -          -
Request 2      -           -          -
Request 3      -           -          X
Request 4      -           -          -
Request 5      -           -          -
Request 6      -           -          -
Request 7      X           -          -
Request 8      -           -          -
Request 9      -           -          X
Request 10     -           -          -
Request 11     -           -          -
Request 12     -           -          -
Request 13     X           -          -
Request 14     -           -          -

Can anyone explain how the onClick handler for one button is activating buttons six rows below? And why every 6th button? This is baffling me.

Here is the code for my custom adapter:

public class EloquenceMessageAdapterStaff
        extends ArrayAdapter<GlobalMessage>
{
    ArrayList<GlobalMessage> roomMessages;

    public EloquenceMessageAdapterStaff(Context context, int textViewResourceId, ArrayList<GlobalMessage> roomMessages){
        super(context, textViewResourceId, roomMessages);

        this.roomMessages = roomMessages;

        int count = roomMessages.size();
        Log.i("Eloquence","roomMessages adapter count=" + Integer.toString(count) );
    }

    public View getView(int position, View convertView, ViewGroup parent){

        View v = convertView;

        if(v == null){
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(R.layout.messagestaff, null);
        }

        GlobalMessage md = roomMessages.get(position);

        if (md != null) {

            int messageId = md.getId();
            String messageIdString = Integer.toString(messageId);
            String messageText= md.getText();
            Date messageTime = md.getInitiatedTime();
            PNMCode messagePnm = md.getPnmCode();
            String messageFullText = messagePnm.getFullText();


            TextView mp_text = (TextView) v.findViewById(R.id.mp_text);
            TextView mp_timedate = (TextView) v.findViewById(R.id.mp_timedate);

            DateFormat dateformat = new SimpleDateFormat("h:mm a, E d");
            String datetext = dateformat.format(messageTime);

            if (mp_text != null){
                mp_text.setText( messageFullText );
            }
            if (mp_timedate != null){
                mp_timedate.setText( datetext );
            }

            final ImageButton button_completed = (ImageButton) v.findViewById(R.id.button_messagestatus_Completed);
            final ImageButton button_inprocess = (ImageButton) v.findViewById(R.id.button_messagestatus_InProcess);
            final ImageButton button_cancel    = (ImageButton) v.findViewById(R.id.button_messagestatus_Cancel);

            button_completed.setTag(R.id.tag_message_id,messageIdString);
            button_completed.setTag(R.id.tag_message_status,"Completed");

            button_inprocess.setTag(R.id.tag_message_id,messageIdString);
            button_inprocess.setTag(R.id.tag_message_status,"InProcess");

            button_cancel.setTag(R.id.tag_message_id,messageIdString);
            button_cancel.setTag(R.id.tag_message_status,"Canceled");


            button_completed.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    if ( button_completed.isActivated() ) {
                        AppInfo.messageButtons.remove(button_completed);
                        button_completed.setActivated(false);
                    } else {
                        AppInfo.messageButtons.add(button_completed);
                        button_completed.setActivated(false);
                        button_inprocess.setActivated(false);
                        button_cancel.setActivated(false);
                        button_completed.setActivated(true);
                    }
                }
            });
            button_inprocess.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    if ( button_inprocess.isActivated() ) {
                        AppInfo.messageButtons.remove(button_inprocess);
                        button_inprocess.setActivated(false);
                    } else {
                        AppInfo.messageButtons.add(button_inprocess);
                        button_completed.setActivated(false);
                        button_inprocess.setActivated(false);
                        button_cancel.setActivated(false);
                        button_inprocess.setActivated(true);
                    }
                }
            });
            button_cancel.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    if ( button_cancel.isActivated() ) {
                        AppInfo.messageButtons.remove(button_cancel);
                        button_cancel.setActivated(false);
                    } else {
                        AppInfo.messageButtons.add(button_cancel);
                        button_completed.setActivated(false);
                        button_inprocess.setActivated(false);
                        button_cancel.setActivated(false);
                        button_cancel.setActivated(true);
                    }
                }
            });
        }
        return v;
    }
}

Here is my main layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    android:paddingLeft="20dp"
    android:paddingRight="20dp"
    >
    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />
    <TextView
        android:id="@android:id/empty"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text=""
        />
</LinearLayout>

and here is my row layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp"
>
    <TextView
        android:id="@+id/mp_timedate"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:text=""
        android:layout_alignParentLeft="true"
        />
    <TextView
        android:id="@+id/mp_text"
        android:layout_width="336dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:text=""
        android:layout_toRightOf="@id/mp_timedate"
        />
    <ImageButton android:id="@+id/button_messagestatus_Completed"
        style="@style/buttontight"
        android:src="@drawable/ic_menu_mark"
        android:layout_toRightOf="@id/mp_text"
        android:contentDescription="Mark message completed"
        />
    <ImageButton android:id="@+id/button_messagestatus_InProcess"
        style="@style/buttontight"
        android:src="@drawable/ic_menu_stopwatch"
        android:layout_toRightOf="@id/button_messagestatus_Completed"
        android:contentDescription="Mark message In Process"
        />
    <ImageButton android:id="@+id/button_messagestatus_Cancel"
        style="@style/buttontight"
        android:src="@drawable/ic_menu_block"
        android:layout_toRightOf="@id/button_messagestatus_InProcess"
        android:contentDescription="Mark message cancelled"
        />
</RelativeLayout>
  • 1
    Please see my answer here: [Listview duplicates item every 6 times](http://stackoverflow.com/a/17800061/2558882). – Vikram Jul 25 '13 at 00:10

1 Answers1

1

The views in a ListView are recycled. So even though there are 14 items in your list, only 6 views (rows) are actually created. This is where convertView comes into play. If your adapter uses convertView, that means it's using a View that has been set up with another item's information. The problem is that the buttons of that converted view is maintaining the state of the previous item that was bound to it. So in getView, you'll want to do something to set the state of the buttons to match the GlobalMessage that they're representing.

Coeffect
  • 8,772
  • 2
  • 27
  • 42
  • 2
    Ok, I've read up on the recycling of views and I understand what you are saying. The reuse of rows means you can't store state, even temporarily, in the view. I need to move the storage of what is selected to the data model. That way, when views are recycled, they will be guaranteed to display the correct info. – Vaughn Teegarden Jul 25 '13 at 13:44