java Android:具有两种不同视图的 EfficientAdapter
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1660417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android: EfficientAdapter with two different Views
提问by znq
I'm using an extended version of BaseAdapter based on the EfficientAdapter examplefrom the SDK demo samples.
我正在使用基于SDK 演示示例中的EfficientAdapter 示例的 BaseAdapter 的扩展版本。
My data is basically an object (ListPlaces) which holds an ArrayListwith the actual list of places, accessible via listPlaces.getValues(). This ArrayList data is sorted by range and the ArrayListconsist of some special items (separators), with no data, but a separatorflag set to true.
我的数据基本上是一个对象 ( ListPlaces),它包含一个ArrayList包含实际地点列表的对象,可通过listPlaces.getValues(). 此 ArrayList 数据按范围排序,ArrayList由一些特殊项(分隔符)组成,没有数据,但separator标志设置为true。
Now whenever my EfficientAdaptergets a data object which is a separator it returns falsefor public boolean isEnabled(int position)and public View getView(int position, View convertView, ViewGroup parent)inflates two different layouts depending on if the current data object consists of real data or is just a separator dummy.
现在,每当我EfficientAdapter获得一个作为分隔符的数据对象时,它就会返回并根据当前数据对象是由真实数据组成还是只是一个分隔符虚拟对象false来膨胀两个不同的布局。public boolean isEnabled(int position)public View getView(int position, View convertView, ViewGroup parent)
This works great, if I inflate the layout every time. However, inflating the layout every time and calling findViewByIdmakes the ListViewalmost unusabely slow.
这很好用,如果我每次都夸大布局。但是,每次都夸大布局并调用findViewById会使ListView速度几乎无法使用。
So I tried to use the EfficientAdapter with ViewHolderapproach. But that didn't work right out of the box, because of the two different views I try to access. So whenever my convertView != null(the else-case) accesses the items on the layout via our ViewHolderand when the previous View was a separator it of course doesn't work to access a TextView there which is only available on the "real" items layout.
所以我尝试使用 EfficientAdapterViewHolder方法。但这并不是开箱即用的,因为我尝试访问两种不同的视图。因此,每当我convertView != null(其他情况)通过我们访问布局上的项目ViewHolder时,当前一个 View 是分隔符时,它当然无法访问仅在“真实”项目布局上可用的 TextView。
So I also force my getView()to inflate the layout not only when convertView == null, but also when the previous listRow is different than the current one: if (convertView == null || (listRow != listRow_previous)) { [....] }
因此,我还强制我getView()不仅在 时膨胀布局convertView == null,而且在前一个 listRow 与当前列表行不同时:if (convertView == null || (listRow != listRow_previous)) { [....] }
This seems to almost work now. Or at least it doesn't crash right from the beginning. But it still crashes and I don't know what I've to do different. I've tried to look into convertView.getID()and convertView.getResources(), but that wasn't really helpful so far. Maybe someone else has an idea how I can check whether my current convertViewmatches with the list item layout or the list separator layout. Thanks.
这似乎现在几乎可以工作了。或者至少它不会从一开始就崩溃。但它仍然崩溃,我不知道我必须做些什么不同的事情。我试图调查convertView.getID()and convertView.getResources(),但到目前为止这并没有真正的帮助。也许其他人知道如何检查我当前是否convertView与列表项布局或列表分隔符布局匹配。谢谢。
Here's the code. Where ever there is a [...] I took out some less important code to make it easier to read and understand:
这是代码。凡是有 [...] 的地方,我都去掉了一些不太重要的代码,以便于阅读和理解:
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;
}
}
And here my Logcatoutput:
这里是我的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)
回答by CommonsWare
You forgot a couple of methods you need to override: getViewTypeCount()and getItemViewType(). These are not needed for lists where all rows are the same, but they are very important for your scenario. Implement these properly, and Android will maintain separate object pools for your headers and detail rows.
您忘记了几个需要覆盖的方法:getViewTypeCount()和getItemViewType()。所有行都相同的列表不需要这些,但它们对于您的场景非常重要。正确实现这些,Android 将为您的标题和详细信息行维护单独的对象池。
Or, you could look at:
或者,你可以看看:
- Jeff Sharkey's original SeparatedListAdapter(GPL)
- My updated rendition(GPL)
- My MergeAdapter, which can be used for this scenario as well (Apache)
- Jeff Sharkey 的原始分离列表适配器(GPL)
- 我的更新版本(GPL)
- 我的MergeAdapter,也可以用于这个场景(Apache)
回答by znq
Thanks to the hint with getViewTypeCount() and getItemViewType() it works perfectly now.
多亏了 getViewTypeCount() 和 getItemViewType() 的提示,它现在可以完美运行。
Implementing these two methods was very 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;
}
As commonswarementioned in his answer this way Android will maintain different object pools for different list items, which also means you can remove the check for listRow_previousin my example and change the if (convertView == null || (listRow != listRow_previous))to if (convertView == null)only.
正如commonsware在他的回答中提到的那样,Android 将为不同的列表项维护不同的对象池,这也意味着您可以listRow_previous在我的示例中删除检查并更改if (convertView == null || (listRow != listRow_previous))为if (convertView == null)only。

