Я использую расширенную версию BaseAdapter на основе EfficientAdapter example из демонстрационных образцов SDK.
Мои данные - это в основном объект (ListPlaces
), который содержит ArrayList
с фактическим списком мест, доступным через listPlaces.getValues()
. Данные ArrayList сортируются по диапазону, а ArrayList
состоят из некоторых специальных элементов (разделителей) без данных, но флаг separator
установлен на true
.
Теперь, когда мой EfficientAdapter
получает объект данных, который является разделителем, он возвращает false
для public boolean isEnabled(int position)
и public View getView(int position, View convertView, ViewGroup parent)
раздувает два разных макета в зависимости от того, состоит ли текущий объект данных из реальных данных или просто разделитель фиктивная.
Это отлично работает, если я каждый раз раздуваю макет. Однако раздувание макета каждый раз и вызов findViewById
делает ListView
почти необычайно медленным.
Поэтому я попытался использовать EfficientAdapter с подходом ViewHolder
. Но это не работает прямо из коробки, из-за двух разных взглядов я пытаюсь получить доступ. Поэтому всякий раз, когда мой convertView != null
(else-case) обращается к элементам макета через наш ViewHolder
, и когда предыдущий View был разделителем, он, конечно, не работает для доступа к TextView там, который доступен только на "реальные" элементы.
Поэтому я также заставляю мой getView()
раздувать макет не только при convertView == null
, но также, когда предыдущий listRow отличается от текущего: if (convertView == null || (listRow != listRow_previous)) { [....] }
Теперь это почти работает. Или, по крайней мере, это не срывается с самого начала. Но он все еще падает, и я не знаю, что мне делать по-другому. Я попытался изучить convertView.getID()
и convertView.getResources()
, но пока это не очень полезно. Возможно, у кого-то есть идея, как я могу проверить, совпадает ли текущий текущий convertView
с макетом списка или раскладкой разделителя списков. Спасибо.
Вот код. Где бы то ни было [...] я вынул еще менее важный код, чтобы было легче читать и понимать:
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;
}
}
И вот мой 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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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: [email protected]
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)