在Android开发页面中,我们对ListViews的Custom ArrayAdapter有以下引用:
"绝对不能保证调用getView()的顺序也不能保证多少次。"
当我有以下内容并且多次调用getView时,它没有问题,因为我只是回收了视图而不是再次对它们进行充气:
@Override
public void getView(int position, View convertView, ViewGroup parent){
Log.i(TAG, "getView of position " + String.valueOf(position) + " is called");
View view = convertView;
if(view == null){
view = inflater.inflate(layoutResourceId, parent, false);
TextView myTextView1 = (TextView) view.findViewById(R.id.my_tv1);
Button myButton1 = (Button) view.findViewById(R.id.my_btn1);
... // More views
view.setTag(new MyViewHolder(myTextView1, myButton1, ...));
}
MyViewHolder h = (MyViewHolder) view.getTag();
h.myTextView1.setText("Some text");
h.myButton1.setText("Some button text");
h.myButton1.setEnabled(buttonEnabled);
}
使用if(view == null) -> inflate view and create ViewHolder; else -> use ViewHolder of Tag
的问题是当您在列表中快速向上和向下滚动时,列表中项目的顺序会发生变化。可能是因为引用的第一部分:
"绝对不能保证调用getView()的顺序......"
虽然我认为它只是ListViews本身而不是适配器的错误。 (不确定,但无论原因是什么,都是一个BUG。)
到目前为止,我能够解决这个问题的唯一方法就是让视图膨胀:
@Override
public void getView(int position, View convertView, ViewGroup parent){
Log.i(TAG, "getView of position " + String.valueOf(position) + " is called");
// NOTE: In Android it's recommended to use the convertView as the view, instead of
// retrieving it every time from the inflater. But since there is a bug in Android ListViews
// which changes the order of the Items when you scroll up- and down real fast,
// I use the inflater every time and ignore the convertView parameter
view = inflater.inflate(layoutResourceId, parent, false);
TextView myTextView1 = (TextView) view.findViewById(R.id.my_tv1);
Button myButton1 = (Button) view.findViewById(R.id.my_btn1);
... // More views
view.setTag(new MyViewHolder(myTextView1, myButton1, ...));
MyViewHolder h = (MyViewHolder) view.getTag();
h.myTextView1.setText("Some text");
h.myButton1.setText("Some button text");
h.myButton1.setEnabled(buttonEnabled);
}
一切正常,当快速上下滚动时,项目的顺序仍然存在。但现在性能正在迅速下降..很长一段时间我都忽略了这个性能问题,但是现在我在对项目布局进行了一些更改后面临一个新问题。从来没有遇到过这个问题,但是现在我(因为一些未知的原因)面对报价的第二部分:
"绝对不能保证会调用多少次getView()..."
现在,ListView包含14个可见元素。当我使用此适配器转到包含ListView的ChecklistActivity时,我收到以下Logcat消息:
07-23 03:17:14.516: V/MyAdapter(1395): getView(0)
07-23 03:17:16.556: V/MyAdapter(1395): getView(1)
07-23 03:17:16.946: V/MyAdapter(1395): getView(2)
07-23 03:17:17.316: V/MyAdapter(1395): getView(3)
07-23 03:17:17.776: V/MyAdapter(1395): getView(4)
07-23 03:17:18.136: V/MyAdapter(1395): getView(5)
07-23 03:17:18.286: V/MyAdapter(1395): getView(6)
07-23 03:17:18.466: V/MyAdapter(1395): getView(7)
07-23 03:17:18.576: V/MyAdapter(1395): getView(8)
07-23 03:17:18.806: V/MyAdapter(1395): getView(9)
07-23 03:17:19.016: V/MyAdapter(1395): getView(10)
07-23 03:17:19.246: V/MyAdapter(1395): getView(11)
07-23 03:17:19.346: D/dalvikvm(1395): GC_FOR_ALLOC freed 220K, 10% free 5545K/6108K, paused 82ms, total 85ms
07-23 03:17:19.526: V/MyAdapter(1395): getView(12)
07-23 03:17:19.666: V/MyAdapter(1395): getView(13)
07-23 03:17:19.786: V/MyAdapter(1395): getView(14)
07-23 03:17:20.046: I/Choreographer(1395): Skipped 1491 frames! The application may be doing too much work on its main thread.
07-23 03:17:20.066: V/ChecklistActivity(1395): onCreateOptionsMenu
07-23 03:17:20.406: I/Choreographer(1395): Skipped 64 frames! The application may be doing too much work on its main thread.
07-23 03:17:20.416: V/MyAdapter(1395): getView(2)
07-23 03:17:20.576: V/MyAdapter(1395): getView(3)
07-23 03:17:20.866: V/MyAdapter(1395): getView(4)
07-23 03:17:21.036: V/MyAdapter(1395): getView(5)
07-23 03:17:21.206: V/MyAdapter(1395): getView(6)
07-23 03:17:21.396: V/MyAdapter(1395): getView(7)
07-23 03:17:21.606: V/MyAdapter(1395): getView(8)
07-23 03:17:21.866: V/MyAdapter(1395): getView(9)
07-23 03:17:22.116: V/MyAdapter(1395): getView(10)
07-23 03:17:22.336: V/MyAdapter(1395): getView(11)
07-23 03:17:22.496: V/MyAdapter(1395): getView(12)
07-23 03:17:22.646: V/MyAdapter(1395): getView(13)
07-23 03:17:22.806: V/MyAdapter(1395): getView(14)
07-23 03:17:23.616: V/MainActivity(1395): onStop
07-23 03:17:23.616: I/Choreographer(1395): Skipped 160 frames! The application may be doing too much work on its main thread.
07-23 03:17:24.246: I/Choreographer(1395): Skipped 112 frames! The application may be doing too much work on its main thread.
07-23 03:17:24.286: V/MyAdapter(1395): getView(4)
07-23 03:17:24.396: D/dalvikvm(1395): GC_FOR_ALLOC freed 397K, 8% free 6099K/6568K, paused 100ms, total 107ms
07-23 03:17:24.646: V/MyAdapter(1395): getView(5)
07-23 03:17:24.756: V/MyAdapter(1395): getView(6)
07-23 03:17:24.886: V/MyAdapter(1395): getView(7)
07-23 03:17:25.046: V/MyAdapter(1395): getView(8)
07-23 03:17:25.196: V/MyAdapter(1395): getView(9)
07-23 03:17:25.356: V/MyAdapter(1395): getView(10)
07-23 03:17:25.486: V/MyAdapter(1395): getView(11)
07-23 03:17:25.596: V/MyAdapter(1395): getView(12)
07-23 03:17:25.726: V/MyAdapter(1395): getView(13)
07-23 03:17:25.846: V/MyAdapter(1395): getView(14)
07-23 03:17:26.146: I/Choreographer(1395): Skipped 35 frames! The application may be doing too much work on its main thread.
07-23 03:17:26.406: V/MyAdapter(1395): getView(6)
07-23 03:17:26.586: V/MyAdapter(1395): getView(7)
07-23 03:17:26.786: V/MyAdapter(1395): getView(8)
07-23 03:17:26.896: V/MyAdapter(1395): getView(9)
07-23 03:17:27.156: V/MyAdapter(1395): getView(10)
07-23 03:17:27.296: V/MyAdapter(1395): getView(11)
07-23 03:17:27.436: V/MyAdapter(1395): getView(12)
07-23 03:17:27.646: V/MyAdapter(1395): getView(13)
07-23 03:17:27.766: V/MyAdapter(1395): getView(14)
07-23 03:17:28.186: I/Choreographer(1395): Skipped 56 frames! The application may be doing too much work on its main thread.
07-23 03:17:28.426: D/dalvikvm(1395): GC_FOR_ALLOC freed 523K, 9% free 6713K/7308K, paused 108ms, total 112ms
07-23 03:17:28.506: I/Choreographer(1395): Skipped 60 frames! The application may be doing too much work on its main thread.
07-23 03:17:28.606: V/MyAdapter(1395): getView(8)
07-23 03:17:28.806: V/MyAdapter(1395): getView(9)
07-23 03:17:28.926: V/MyAdapter(1395): getView(10)
07-23 03:17:29.136: V/MyAdapter(1395): getView(11)
07-23 03:17:29.256: V/MyAdapter(1395): getView(12)
07-23 03:17:29.406: V/MyAdapter(1395): getView(13)
07-23 03:17:29.516: V/MyAdapter(1395): getView(14)
07-23 03:17:29.816: I/Choreographer(1395): Skipped 35 frames! The application may be doing too much work on its main thread.
07-23 03:17:30.096: V/MyAdapter(1395): getView(9)
07-23 03:17:30.216: V/MyAdapter(1395): getView(10)
07-23 03:17:30.366: V/MyAdapter(1395): getView(11)
07-23 03:17:30.496: V/MyAdapter(1395): getView(12)
07-23 03:17:30.646: V/MyAdapter(1395): getView(13)
07-23 03:17:30.796: V/MyAdapter(1395): getView(14)
07-23 03:17:31.166: I/Choreographer(1395): Skipped 53 frames! The application may be doing too much work on its main thread.
07-23 03:17:31.376: I/Choreographer(1395): Skipped 32 frames! The application may be doing too much work on its main thread.
07-23 03:17:31.426: V/MyAdapter(1395): getView(10)
07-23 03:17:31.546: V/MyAdapter(1395): getView(11)
07-23 03:17:31.736: V/MyAdapter(1395): getView(12)
07-23 03:17:31.906: V/MyAdapter(1395): getView(13)
07-23 03:17:32.046: V/MyAdapter(1395): getView(14)
07-23 03:17:32.306: I/Choreographer(1395): Skipped 31 frames! The application may be doing too much work on its main thread.
07-23 03:17:32.536: I/Choreographer(1395): Skipped 35 frames! The application may be doing too much work on its main thread.
07-23 03:17:32.606: V/MyAdapter(1395): getView(11)
07-23 03:17:32.816: V/MyAdapter(1395): getView(12)
07-23 03:17:32.986: D/dalvikvm(1395): GC_FOR_ALLOC freed 729K, 10% free 7323K/8124K, paused 121ms, total 128ms
07-23 03:17:33.166: V/MyAdapter(1395): getView(13)
07-23 03:17:33.496: V/MyAdapter(1395): getView(14)
07-23 03:17:34.056: V/MyAdapter(1395): getView(12)
07-23 03:17:34.186: V/MyAdapter(1395): getView(13)
07-23 03:17:34.316: V/MyAdapter(1395): getView(14)
07-23 03:17:34.596: I/Choreographer(1395): Skipped 34 frames! The application may be doing too much work on its main thread.
07-23 03:17:34.846: I/Choreographer(1395): Skipped 36 frames! The application may be doing too much work on its main thread.
07-23 03:17:34.906: V/MyAdapter(1395): getView(13)
07-23 03:17:35.016: V/MyAdapter(1395): getView(14)
07-23 03:17:35.546: V/MyAdapter(1395): getView(14)
07-23 03:17:36.066: I/Choreographer(1395): Skipped 31 frames! The application may be doing too much work on its main thread.
我完全不知道为什么这么多次被召唤,但我希望每次只召唤一次。如果有人知道这个快速滚动错误的解决方案,它会改变订单而不会在getView()中每次都膨胀View,这也将是一个巨大的帮助。
PS:忽略我目前正在处理的第一个07-23 03:17:20.046: I/Choreographer(1395): Skipped 1491 frames! The application may be doing too much work on its main thread.
..充气视图是性能缺乏的原因之一,但我还有一些其他问题试图解决。
但是,为什么Android不能拥有强大的ArrayAdapter。当有人快速上下滚动时,不会改变项目顺序的那个。或者一个选项,这样你就可以选择记住可见区域之外的项目..如果你记得很多项目,这理论上也会产生性能问题,但它会解决很多问题,包括那些再次重置EditText输入的问题在它们离开可见区域之后。那么我们就不应该使用TextWatchers,带有持有人的标签等......
好的,我现在正在旁观......它的方式就是这样,我无法改变它。因此,让我们只关注修复上面解释的问题(快速滚动错误,不会每次都膨胀而且getView在创建时不会被调用多次)。
编辑1:
有关我代码其他部分的更多信息:
checklist_activity.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants" />
</RelativeLayout>
list_item.xml外部布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- height is wrap content since I have views that are Visible / Gone based on the Item's state -->
<!-- All my View elements -->
</RelativeLayout>
对于我的项目,请参阅this different stackoverflow question of mine。
编辑2:
下面是我当前的MyAdapter.java类的副本(PS:删除了注释,导入和日志以便于阅读)。当我在模拟器中快速向上和向下滚动时,图像保持不变,但文本(产品名称,价格等)正在混淆。我有一个按类别和产品名称排序的列表,我想列出在ListView中保留此订单。
public class MyAdapter extends ArrayAdapter<OrderedProductItem>
{
private static final String TAG = "MyAdapter";
public static MyAdapter mAdapter;
private Context context;
private int layoutResourceId;
private LayoutInflater inflater;
public MyAdapter(Context c, int layoutId, ArrayList<OrderedProductItem> objects){
super(c, layoutId, objects);
layoutResourceId = layoutId;
context = c;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mAdapter = this;
}
private OnTouchListener itemOnTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(v instanceof EditText){
EditText et = (EditText)v;
et.setFocusable(true);
et.setFocusableInTouchMode(true);
}
else{
ListItemTagHolder h = (ListItemTagHolder) v.getTag();
h.etAmount.setFocusable(false);
h.etAmount.setFocusableInTouchMode(false);
h.actvName.setFocusable(false);
h.actvName.setFocusableInTouchMode(false);
}
return false;
}
};
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
ListItemTagHolder h;
if(view == null){
view = inflater.inflate(layoutResourceId, parent, false);
RelativeLayout rl = (RelativeLayout) view.findViewById(R.id.item_layout);
h = new ListItemTagHolder(rl);
view.setTag(h);
}
else
h = (ListItemTagHolder) view.getTag();
/*
View view = inflater.inflate(layoutResourceId, parent, false);
RelativeLayout rl = (RelativeLayout) view.findViewById(R.id.item_layout);
view.setTag(new ListItemTagHolder(rl));
final ListItemTagHolder h = (ListItemTagHolder) view.getTag();
*/
if(h != null){
h.orderedProductItem = Controller.getInstance().getOrderedProductItems().get(position);
MainActivity.getImageLoader().imageForImageView(h.orderedProductItem.getCheckState().rID(), h.imageView);
String productName = Controller.getInstance().getProductById(h.orderedProductItem.getProductId()).getName();
// (I use a Validation-class to compare Strings or Check if they aren't null/empty)
if(!V.compare(h.tvName.getText().toString(), productName, true, true))
h.tvName.setText(productName);
String priceString = C.doubleToPrice(Controller.getInstance().getProductById(h.orderedProductItem.getProductId()).getPrice(), "€", true);
if(!V.compare(h.tvPrice.getText().toString(), priceString, true, true))
h.tvPrice.setText(priceString);
String resultAmount = String.valueOf(h.orderedProductItem.getResultAmount());
if(!V.compare(h.etAmount.getText().toString(), resultAmount, true, true))
h.etAmount.setText(resultAmount);
ChecklistActivity.cActivity.updateAutoCompleteTextViewWithFilter(h, h.orderedProductItem.getSelectedFilter());
ChecklistActivity.cActivity.updateTagsSpinner(h, null);
h.etAmount.addTextChangedListener(new MyTextWatcher(h.etAmount));
h.actvName.addTextChangedListener(new MyTextWatcher(h.actvName));
h.etAmount.setTag(h.orderedProductItem);
h.actvName.setTag(h.orderedProductItem);
ChecklistActivity.cActivity.updateChangeEditTexts(view, h);
view.setOnTouchListener(itemOnTouchListener);
h.etAmount.setOnTouchListener(itemOnTouchListener);
h.actvName.setOnTouchListener(itemOnTouchListener);
}
return view;
}
}
使用以下Holder级别:
public class ListItemTagHolder
{
public final RelativeLayout relativeLayout;
public final LinearLayout leftLL, rightLL;
public final ImageView imageView;
public final TextView tvName, tvPrice, tvTags;
public final EditText etAmount;
public final AutoCompleteTextView actvName;
public final Spinner spTags;
public final ImageButton btnTags;
public final Space spaceImage, spacePrice;
public OrderedProductItem orderedProductItem;
public ListItemTagHolder(RelativeLayout layout){
relativeLayout = layout;
leftLL = (LinearLayout) relativeLayout.findViewById(R.id.left_ll);
rightLL = (LinearLayout) relativeLayout.findViewById(R.id.right_ll);
imageView = (ImageView) relativeLayout.findViewById(R.id.image);
tvName = (TextView) relativeLayout.findViewById(R.id.tv_product_name);
tvPrice = (TextView) relativeLayout.findViewById(R.id.tv_price);
tvTags = (TextView) relativeLayout.findViewById(R.id.tv_tags);
etAmount = (EditText) relativeLayout.findViewById(R.id.et_result_amount);
actvName = (AutoCompleteTextView) relativeLayout.findViewById(R.id.actv_result_name);
spTags = (Spinner) relativeLayout.findViewById(R.id.sp_tags);
btnTags = (ImageButton) relativeLayout.findViewById(R.id.btn_tags);
spaceImage = (Space) relativeLayout.findViewById(R.id.filler_space_image);
spacePrice = (Space) relativeLayout.findViewById(R.id.filler_space_price);
}
}
PS:我知道大多数人一个接一个地把所有View元素都放在Holder中,而我曾经有过这个。但是从布局中逐一获取它们或逐个地解决我面临的问题没有任何区别。
PSS:这三个更新方法重新应用Spinner / EditTexts / AutoCompleteTextView中OrderedProductItem字段的新数据。我已将它们放在ChecklistActivity中,因为我也在那里使用相同的方法。这对我面临的问题也没有任何影响,无论是直接将它们放在getView方法中,还是像我在这里一样调用它们。
答案 0 :(得分:3)
我建议您稍微更改一下代码。我的ListView的getView()总是看起来像这样,我没有遇到任何问题。
@Override
public void getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
MyViewHolder holder = new MyViewHolder();
if (view == null) {
view = inflater.inflate(layoutResourceId, null);
holder.textView1 = (TextView) view.findViewById(R.id.my_tv1);
holder.button1 = (Button) view.findViewById(R.id.my_btn1);
// More views
view.setTag(holder);
} else {
holder = (MyViewHolder) view.getTag();
}
holder.textView1.setText("Some text");
holder.button1.setText("Some button text");
holder.button1.setEnabled(buttonEnabled);
}
public static class MyViewHolder {
public TextView textView1;
public Button button1;
// ... and so on
}
答案 1 :(得分:1)
这不是错误!
getView()调用了多少次并不重要。每次android想要在Listview中显示一行时都会调用此方法。
您可以覆盖Adapter的getItem(int position),以便在每个位置返回您想要的每个Object。除此之外,请检查list_item高度。
@Override
public Object getItem(int position) {
Object item = null;
if (position >= 0 && position < mData.size()) {
item = mData.get(position);
}
else {
Log.e(TAG, "Bad position in getItem");
}
return item;
}
mData是绑定到listview的数据的ArrayList。
请注意,在getView()中,您应该为所有视图设置值。这很重要。
答案 2 :(得分:0)
为什么要在ListItemTagHolder类中保存OrderedProductItem对象? 你总是用这个命令获取数据
getItem(position)
并且您不需要在ListItemTagHolder类中保存此对象。也许这个命令
h.orderedProductItem = Controller.getInstance().getOrderedProductItems().get(position);
返回错误数据!! 我想如果你更改上面的命令并从ListItemTagHolder类中移出,你的问题将得到解决。 :)