Android: EfficientAdapter avec deux vues différentes
Question
J'utilise une version étendue de BaseAdapter sur la base EfficientAdapter exemple à partir des échantillons de démonstration SDK.
Mes données est essentiellement un objet (ListPlaces
) qui est titulaire d'un ArrayList
avec la liste actuelle des lieux, accessibles via listPlaces.getValues()
. Ces données ArrayList sont triées par gamme et la ArrayList
se composent de certains éléments spéciaux (séparateurs), sans données, mais un drapeau separator
réglé sur true
.
Maintenant, chaque fois que mon EfficientAdapter
obtient un objet de données qui est un séparateur retourne false
pour public boolean isEnabled(int position)
et public View getView(int position, View convertView, ViewGroup parent)
déploie deux configurations différentes selon que l'objet de données en cours est constitué de données réelles ou est juste un mannequin de séparation.
Cela fonctionne très bien, si je gonfle la mise en page à chaque fois. Cependant, gonfler la mise en page à chaque fois et appelant findViewById
rend le ListView
presque lent unusabely.
J'ai donc essayé d'utiliser le EfficientAdapter avec l'approche de ViewHolder
. Mais cela ne fonctionne pas dès la sortie de la boîte, à cause des deux points de vue différents, j'essaie d'accéder. Donc, chaque fois que mon convertView != null
(l'autre cas) donne accès aux éléments de la mise en page via notre ViewHolder
et quand la précédente vue était un séparateur bien sûr, il ne fonctionne pas pour accéder à un TextView il disponible uniquement sur la mise en page des éléments « réels » .
Alors je force aussi mon getView()
pour gonfler la disposition non seulement quand convertView == null
, mais aussi lorsque la listRow précédente est différente de celle actuelle: if (convertView == null || (listRow != listRow_previous)) { [....] }
Cela semble fonctionner presque maintenant. Ou du moins il ne tombe pas en panne dès le début. Mais il se bloque encore et je ne sais pas ce que j'ai à faire différent. J'ai essayé de regarder dans convertView.getID()
et convertView.getResources()
, mais cela n'a pas été très utile à ce jour. Peut-être que quelqu'un d'autre a une idée comment je peux vérifier si mon convertView
courant correspond à la mise en page de l'élément de liste ou la mise en page de séparateur de liste. Merci.
Voici le code. Partout où il y a un [...] Je pris un code moins important pour le rendre plus facile à lire et à comprendre:
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;
}
}
Et voici mon Logcat Sortie:
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)
La solution
Vous avez oublié deux méthodes que vous devez passer outre: getViewTypeCount () et getItemViewType () . Ceux-ci ne sont pas nécessaires pour les listes où toutes les lignes sont les mêmes, mais ils sont très importants pour votre scénario. Mettre en œuvre ces correctement et Android maintiendra pools d'objets séparés pour vos en-têtes et les lignes de détails.
Ou, vous pouvez regarder:
- SeparatedListAdapter (GPL)
- Mon interprétation mis à jour (GPL)
- Mon MergeAdapter , qui peut être utilisé pour ce scénario et (Apache)
Autres conseils
Merci à l'indice avec getViewTypeCount () et getItemViewType () il fonctionne parfaitement maintenant.
La mise en œuvre de ces deux méthodes a été très 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;
}
commonsware mentionné dans sa réponse de cette façon Android maintiendra différents pools d'objets pour les différents éléments de la liste, ce qui signifie également que vous pouvez retirer le chèque de listRow_previous
dans mon exemple et changer le if (convertView == null || (listRow != listRow_previous))
à if (convertView == null)
seulement.