I need to add custom button objects to each row in a ListView. Here's a simplified row layout:
<LinearLayout android:id="@+id/table_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView android:id="@+id/label"
android:textSize="19dp"
android:textStyle="bold"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:lines="1"
/>
<LinearLayout android:id="@+id/button_wrapper"
android:layout_width="100dp"
android:layout_height="match_parent"
/>
</LinearLayout>
In my custom ArrayAdapter, I place the button into the cell in getView():
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// recycle the cell if possible
View cell = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
cell = inflater.inflate(R.layout.table_cell, parent, false);
} else {
cell = convertView;
}
MyButton button = (MyButton) this.buttons.get(position);
if (button != null) {
// remove the button from the previous instance of this cell
ViewGroup parent = (ViewGroup)button.getParent();
if (parent != null) {
parent.removeView(button);
}
// add the button to the new instance of this cell
ViewGroup buttonWrapper = (ViewGroup)cell.findViewById(R.id.button_wrapper);
buttonWrapper.addView(button);
}
}
I know that getView() is called multiple times for each table row as I scroll the table or click buttons or do other things, so the code above removes the button from the previous view before adding it to the new view to avoid a "view already has a parent" exception.
The problem is that this assumes the latest view generated from getView is the one that's visible on the screen, but this is often not the case. Sometimes getView() generates new views, but an older view remains on the screen. In that situation, my button disappears because getView() moves it to a new view that is not visible. I discovered that behavior by initializing an int variable named repeatRowTest
and then adding this code inside getView():
if (position == 0) {
Log.d("getView", "repeat row count: " + repeatRowCountTest);
TextView label = (TextView)cell.findViewById(R.id.label);
label.setText(String.format("%d %s", repeatRowCountTest, label.getText()));
repeatRowCountTest++;
}
This shows me how many times a given row has been generated, and which instance is currently displayed. I might see a row being generated 10 times, while only the 5th one is displayed. But my buttons will only be visible if the latest instance of the row is displayed.
So the question is, how can I tell whether a row generated in getView() is actually going to be displayed, so I know whether to move my button into it, or leave my button where it is? Or more generally, how can I add a button to a row and make sure it remains visible as getView is repeated for a given position?
I've inspected all the properties of a displayed row versus an extra, non-displayed row, and couldn't find any differences. I also tried calling notifyDataSetChanged on the array adapter after my buttons disappear, and that refreshes the list with all the latest views that contain the buttons -- but it's not clear which events trigger getView to repeat itself, so I wouldn't know when I need to call notifyDataSetChanged to make things right again. I suppose I could clone the button and add a new instance of the button to each new instance of the row, but that seems more resource-intensive than is necessary, and will create other problems since other objects have references to these buttons. I haven't found any code examples showing the best way to do this, but it seems like a common requirement, so hopefully I'm missing something simple!
UPDATE: Is there a method of the ArrayAdapter I can override that is called after the getView() methods are called? If so, I could check the parents of all the recently created rows to see if they are actually displayed in the ListView, and refresh the ListView at that point if they aren't.