Android: EfficientAdapter con dos vistas diferentes

Estoy usando una versión extendida de BaseAdapter basada en el ejemplo de EfficientAdapter de las muestras de demostración de SDK.

Mis datos son básicamente un objeto ( ListPlaces ) que contiene un ArrayList con la lista real de lugares, accesible a través de listPlaces.getValues() . Estos datos de ArrayList se ordenan por rango y ArrayList consta de algunos elementos especiales (separadores), sin datos, pero con un indicador de separator establecido en true .

Ahora cada vez que mi EfficientAdapter obtiene un objeto de datos que es un separador, devuelve false para public boolean isEnabled(int position) y public View getView(int position, View convertView, ViewGroup parent) infla dos diseños diferentes dependiendo de si el objeto de datos actual consiste en Datos reales o es solo un dummy separador.

Esto funciona muy bien, si inflo el diseño cada vez. Sin embargo, inflar el diseño cada vez y llamar a findViewById hace que el ListView inusualmente lento.

Así que traté de usar el enfoque EfficientAdapter con ViewHolder . Pero eso no funcionó de inmediato, debido a las dos vistas diferentes a las que trato de acceder. Así que cada vez que mi convertView != null (el caso-else) accede a los elementos en el diseño a través de nuestro ViewHolder y cuando la vista anterior era un separador, por supuesto no funciona para acceder a un TextView allí que solo está disponible en el “real “diseño de elementos.

Así que también getView() mi getView() a inflar el diseño no solo cuando convertView == null , sino también cuando el listRow anterior es diferente al actual: if (convertView == null || (listRow != listRow_previous)) { [....] }

Esto parece casi funcionar ahora. O al menos no se bloquea desde el principio. Pero todavía se cuelga y no sé qué tengo que hacer diferente. He intentado buscar en convertView.getID() y convertView.getResources() , pero hasta ahora eso no fue muy útil. Tal vez alguien más tenga una idea de cómo puedo verificar si mi convertView actual coincide con el diseño del elemento de la lista o el diseño del separador de la lista. Gracias.

Aquí está el código. Donde quiera que haya un […] código, saqué un código menos importante para que sea más fácil de leer y entender:

 private class EfficientAdapter extends BaseAdapter { private LayoutInflater mInflater; private ListPlaces listPlaces; private ListRow listRow; private ListRow listRow_previous; public EfficientAdapter(Context context, ListPlaces listPlaces) { // Cache the LayoutInflate to avoid asking for a new one each time. mInflater = LayoutInflater.from(context); // Data this.listPlaces = listPlaces; } /** * The number of items in the list is determined by the number of items * in our ArrayList * * @see android.widget.ListAdapter#getCount() */ public int getCount() { return listPlaces.getValues().size(); } /** * Since the data comes from an array, just returning the index is * sufficent to get at the data. If we were using a more complex data * structure, we would return whatever object represents one row in the * list. * * @see android.widget.ListAdapter#getItem(int) */ public Object getItem(int position) { return position; } /** * Use the array index as a unique id. * * @see android.widget.ListAdapter#getItemId(int) */ public long getItemId(int position) { return position; } @Override public boolean isEnabled(int position) { // return false if item is a separator: if(listPlaces.getValues().get(position).separator >= 0) return false; else return true; } @Override public boolean areAllItemsEnabled() { return false; } /** * Make a view to hold each row. * * @see android.widget.ListAdapter#getView(int, android.view.View, * android.view.ViewGroup) */ public View getView(int position, View convertView, ViewGroup parent) { // Get the values for the current list element ListPlacesValues curValues = listPlaces.getValues().get(position); if (curValues.separator >= 0) listRow = ListRow.SEPARATOR; else listRow = ListRow.ITEM; Log.i(TAG,"Adapter: getView("+position+") " + listRow + " (" + listRow_previous + ") -> START"); // A ViewHolder keeps references to children views to avoid unneccessary calls // to findViewById() on each row. ViewHolder holder; // When convertView is not null, we can reuse it directly, there is no need // to reinflate it. We only inflate a new View when the convertView supplied // by ListView is null. if (convertView == null || (listRow != listRow_previous)) { Log.i(TAG, "--> (convertView == null) at position: " + position); // Creates a ViewHolder and store references to the two children views // we want to bind data to. holder = new ViewHolder(); if (listRow == ListRow.SEPARATOR) { convertView = mInflater.inflate(R.layout.taxonomy_list_separator, null); holder.separatorText = (TextView) convertView.findViewById(R.id.separatorText); convertView.setTag(holder); Log.i(TAG,"\tCREATE SEPARATOR: convertView ID: " + convertView.getId() + " Resource: " + convertView.getResources()); } else { convertView = mInflater.inflate(R.layout.taxonomy_listitem, null); holder.name = (TextView) convertView.findViewById(R.id.name); holder.category = (TextView) convertView.findViewById(R.id.category); // [...] convertView.setTag(holder); Log.i(TAG,"\tCREATE ITEM: convertView ID: " + convertView.getId() + " Resource: " + convertView.getResources()); } } else { // Get the ViewHolder back to get fast access to the TextView // and the ImageView. Log.i(TAG,"\tconvertView ID: " + convertView.getId() + " Resource: " + convertView.getResources()); holder = (ViewHolder) convertView.getTag(); convertView.setAnimation(null); } /* Bind the data efficiently with the holder */ if (listRow == ListRow.SEPARATOR) { String separatorText; switch (curValues.separator) { case 0: separatorText="case 0"; break; case 1: separatorText="case 1"; break; case 2: separatorText="case 2"; break; // [...] default: separatorText="[ERROR]"; break; } holder.separatorText.setText(separatorText); } else { // Set the name: holder.name.setText(curValues.name); // Set category String cat = curValues.classification.toString(); cat = cat.substring(1,cat.length()-1); // removing "[" and "]" if (cat.length() > 35) { cat = cat.substring(0, 35); cat = cat + "..."; } holder.category.setText(cat); // [...] (and many more TextViews and ImageViews to be set) } listRow_previous = listRow; Log.i(TAG,"Adapter: getView("+position+") -> DONE"); return convertView; } private class ViewHolder { TextView name; TextView category; // [...] -> many more TextViews and ImageViews TextView separatorText; } } 

Y aquí mi salida Logcat :

  755 ListPlaces_Activity I onPostExecute: notifyDataSetChanged() 755 ListPlaces_Activity I Adapter: getView(0) SEPARATOR (null) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 0 755 ListPlaces_Activity I CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(0) -> DONE 755 ListPlaces_Activity I Adapter: getView(1) ITEM (SEPARATOR) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 1 755 ListPlaces_Activity I CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(1) -> DONE 755 ListPlaces_Activity I Adapter: getView(2) SEPARATOR (ITEM) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 2 755 ListPlaces_Activity I CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(2) -> DONE 755 ListPlaces_Activity I Adapter: getView(3) ITEM (SEPARATOR) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 3 755 ListPlaces_Activity I CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(3) -> DONE 755 ListPlaces_Activity I Adapter: getView(4) ITEM (ITEM) -> START 755 ListPlaces_Activity I convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(4) -> DONE 755 ListPlaces_Activity I Adapter: getView(5) ITEM (ITEM) -> START 755 ListPlaces_Activity I convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(5) -> DONE 755 ListPlaces_Activity I Adapter: getView(6) ITEM (ITEM) -> START 755 ListPlaces_Activity I convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(6) -> DONE 755 ListPlaces_Activity I Adapter: getView(0) SEPARATOR (ITEM) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 0 755 ListPlaces_Activity I CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(0) -> DONE 755 ListPlaces_Activity I Adapter: getView(1) ITEM (SEPARATOR) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 1 755 ListPlaces_Activity I CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(1) -> DONE 755 ListPlaces_Activity I Adapter: getView(2) SEPARATOR (ITEM) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 2 755 ListPlaces_Activity I CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(2) -> DONE 755 ListPlaces_Activity I Adapter: getView(3) ITEM (SEPARATOR) -> START 755 ListPlaces_Activity I --> (convertView == null) at position: 3 755 ListPlaces_Activity I CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(3) -> DONE 755 ListPlaces_Activity I Adapter: getView(4) ITEM (ITEM) -> START 755 ListPlaces_Activity I convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 ListPlaces_Activity I Adapter: getView(4) -> DONE 755 ListPlaces_Activity I Adapter: getView(5) ITEM (ITEM) -> START 755 ListPlaces_Activity I convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0 755 AndroidRuntime D Shutting down VM 755 dalvikvm W threadid=3: thread exiting with uncaught exception (group=0x4001aa28) 755 AndroidRuntime E Uncaught handler: thread main exiting due to uncaught exception 755 AndroidRuntime E java.lang.NullPointerException 755 AndroidRuntime E at com.tato.main.ListPlaces_Activity$EfficientAdapter.getView(ListPlaces_Activity.java:330) 755 AndroidRuntime E at android.widget.HeaderViewListAdapter.getView(HeaderViewListAdapter.java:191) 755 AndroidRuntime E at android.widget.AbsListView.obtainView(AbsListView.java:1255) 755 AndroidRuntime E at android.widget.ListView.makeAndAddView(ListView.java:1658) 755 AndroidRuntime E at android.widget.ListView.fillDown(ListView.java:637) 755 AndroidRuntime E at android.widget.ListView.fillFromTop(ListView.java:694) 755 AndroidRuntime E at android.widget.ListView.layoutChildren(ListView.java:1502) 755 AndroidRuntime E at android.widget.AbsListView.onLayout(AbsListView.java:1112) 755 AndroidRuntime E at android.view.View.layout(View.java:6569) 755 AndroidRuntime E at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 755 AndroidRuntime E at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998) 755 AndroidRuntime E at android.widget.LinearLayout.onLayout(LinearLayout.java:918) 755 AndroidRuntime E at android.view.View.layout(View.java:6569) 755 AndroidRuntime E at android.widget.FrameLayout.onLayout(FrameLayout.java:333) 755 AndroidRuntime E at android.view.View.layout(View.java:6569) 755 AndroidRuntime E at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 755 AndroidRuntime E at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998) 755 AndroidRuntime E at android.widget.LinearLayout.onLayout(LinearLayout.java:918) 755 AndroidRuntime E at android.view.View.layout(View.java:6569) 755 AndroidRuntime E at android.widget.FrameLayout.onLayout(FrameLayout.java:333) 755 AndroidRuntime E at android.view.View.layout(View.java:6569) 755 AndroidRuntime E at android.view.ViewRoot.performTraversals(ViewRoot.java:979) 755 AndroidRuntime E at android.view.ViewRoot.handleMessage(ViewRoot.java:1613) 755 AndroidRuntime E at android.os.Handler.dispatchMessage(Handler.java:99) 755 AndroidRuntime E at android.os.Looper.loop(Looper.java:123) 755 AndroidRuntime E at android.app.ActivityThread.main(ActivityThread.java:4203) 755 AndroidRuntime E at java.lang.reflect.Method.invokeNative(Native Method) 755 AndroidRuntime E at java.lang.reflect.Method.invoke(Method.java:521) 755 AndroidRuntime E at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791) 755 AndroidRuntime E at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549) 755 AndroidRuntime E at dalvik.system.NativeStart.main(Native Method) 

Olvidó un par de métodos que necesita reemplazar: getViewTypeCount () y getItemViewType () . Estos no son necesarios para las listas donde todas las filas son iguales, pero son muy importantes para su escenario. Implemente esto correctamente, y Android mantendrá grupos de objetos separados para sus encabezados y filas de detalles.

O, usted podría mirar:

  • SeparatedListAdapter original (GPL) de Jeff Sharkey
  • Mi versión actualizada (GPL)
  • Mi MergeAdapter , que también se puede usar para este escenario (Apache)

Gracias a la sugerencia con getViewTypeCount () y getItemViewType () funciona perfectamente ahora.

Implementar estos dos métodos fue muy simple:

 @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { if(listPlaces.getValues().get(position).separator >= 0) return 0; else return 1; } 

Como commonsware mencionó en su respuesta de esta manera, Android mantendrá diferentes grupos de objetos para diferentes elementos de la lista, lo que también significa que puede eliminar la comprobación de listRow_previous en mi ejemplo y cambiar el if (convertView == null || (listRow != listRow_previous)) a if (convertView == null) solamente.