Android为什么在我的SpinnerAdapter中回收错误的视图类型?

问题描述:

我正在尝试制作具有分隔符的ActionBar微调器。我实施了一个SpinnerAdapter,它有两个项目视图类型(感谢getViewTypeCount)。问题是我正在从其他类型发送一些convertViewsAndroid为什么在我的SpinnerAdapter中回收错误的视图类型?

这里是我的SpinnerAdapter:

public abstract class SeparatorSpinnerAdapter implements SpinnerAdapter { 
    Context mContext; 
    List<Object> mData; 
    int mSeparatorLayoutResId, mActionBarItemLayoutResId, mDropDownItemLayoutResId, mTextViewResId; 

    public static class SpinnerSeparator { 
     public int separatorTextResId; 

     public SpinnerSeparator(final int resId) { 
      separatorTextResId = resId; 
     } 
    } 

    public abstract String getText(int position); 

    public SeparatorSpinnerAdapter(final Context ctx, final List<Object> data, final int separatorLayoutResId, final int actionBarItemLayoutResId, 
      final int dropDownItemLayoutResId, final int textViewResId) { 
     mContext = ctx; 
     mData = data; 
     mSeparatorLayoutResId = separatorLayoutResId; 
     mActionBarItemLayoutResId = actionBarItemLayoutResId; 
     mDropDownItemLayoutResId = dropDownItemLayoutResId; 
     mTextViewResId = textViewResId; 
    } 

    protected String getString(final int resId) { 
     return mContext.getString(resId); 
    } 

    @Override 
    public void registerDataSetObserver(final DataSetObserver observer) { 
    } 

    @Override 
    public void unregisterDataSetObserver(final DataSetObserver observer) { 
    } 

    @Override 
    public int getCount() { 
     if (mData != null) { 
      return mData.size(); 
     } 
     return 0; 
    } 

    @Override 
    public Object getItem(final int position) { 
     return mData == null ? null : mData.get(position); 
    } 

    @Override 
    public boolean isEmpty() { 
     return getCount() == 0; 
    } 

    @Override 
    public long getItemId(final int position) { 
     return 0; 
    } 

    @Override 
    public boolean hasStableIds() { 
     return false; 
    } 

    @Override 
    public View getView(final int position, final View convertView, final ViewGroup parent) { 
     return getView(mActionBarItemLayoutResId, position, convertView, parent); 
    } 

    public boolean isSeparator(final int position) { 
     final Object item = getItem(position); 
     if (item != null) { 
      return item instanceof SpinnerSeparator; 
     } 
     return false; 
    } 

    @Override 
    public int getItemViewType(final int position) { 
     return isSeparator(position) ? 0 : 1; 
    } 

    @Override 
    public int getViewTypeCount() { 
     return 2; 
    } 

    @Override 
    public View getDropDownView(final int position, final View convertView, final ViewGroup parent) { 
     return getView(isSeparator(position) ? mSeparatorLayoutResId : mDropDownItemLayoutResId, position, convertView, parent); 
    } 

    private View getView(final int layoutResId, final int position, final View convertView, final ViewGroup parent) { 
     View v; 

     Log.i("TAG", "getView #" + position + "\tVT=" + getItemViewType(position) + "\tCV=" 
       + (convertView == null ? " null " : convertView.getClass().getSimpleName()) + "\ttext=> " + getText(position)); 

     if (convertView == null) { 
      final LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      v = li.inflate(layoutResId, parent, false); 
     } else { 
      v = convertView; 
     } 

     final TextView tv = (TextView) v.findViewById(mTextViewResId); 
     if (tv != null) { 
      tv.setText(getText(position)); 

      if (isSeparator(position)) { 
       tv.setOnClickListener(null); 
       tv.setOnTouchListener(null); 
      } 
     } 

     return v; 
    } 
} 

一个实现:

public class IssuesMainFilterAdapter extends SeparatorSpinnerAdapter { 
    public IssuesMainFilterAdapter(final Context ctx, final List<Query> queries, final List<Project> projects) { 
     super(ctx, buildDataArray(queries, projects), R.layout.issues_filter_spinner_separator, R.layout.issues_filter_spinner_in_actionbar, 
       R.layout.issues_filter_spinner, R.id.issues_filter_spinner_text); 
    } 

    private static List<Object> buildDataArray(final List<Query> queries, final List<Project> projects) { 
     final List<Object> data = new ArrayList<Object>(); 
     data.add(null); // "ALL" 
     data.add(new SpinnerSeparator(R.string.issue_filter_queries)); 
     data.addAll(queries); 
     data.add(new SpinnerSeparator(R.string.issue_filter_projects)); 
     data.addAll(projects); 
     return data; 
    } 

    @Override 
    public String getText(final int position) { 
     final Object item = getItem(position); 
     if (item == null) { 
      return getString(R.string.issue_filter_all); 
     } else if (item instanceof Query) { 
      return ((Query) item).name; 
     } else if (item instanceof Project) { 
      return ((Project) item).name; 
     } else if (item instanceof SpinnerSeparator) { 
      return getString(((SpinnerSeparator) item).separatorTextResId); 
     } 
     throw new InvalidParameterException("Item has unknown type: " + item); 
    } 
} 

正如你可能已经注意到,我已经设置了日志行到getView()让我更好地了解这是怎么回事:

05-06 14:01:28.721 I/TAG(5879): getView #0 VT=1 CV=TextView text=> #### 
05-06 14:01:28.721 I/TAG(5879): getView #1 VT=0 CV=LinearLayout text=> #### 
05-06 14:01:28.729 I/TAG(5879): getView #2 VT=1 CV=TextView text=> #### 
05-06 14:01:28.745 I/TAG(5879): getView #3 VT=1 CV=TextView text=> #### 
05-06 14:01:28.745 I/TAG(5879): getView #4 VT=0 CV=LinearLayout text=> #### 
05-06 14:01:28.745 I/TAG(5879): getView #5 VT=1 CV=TextView text=> #### 
05-06 14:01:28.753 I/TAG(5879): getView #6 VT=1 CV=TextView text=> #### 
05-06 14:01:28.768 I/TAG(5879): getView #7 VT=1 CV=TextView text=> #### 
05-06 14:01:28.768 I/TAG(5879): getView #8 VT=1 CV=TextView text=> #### 
05-06 14:01:28.768 I/TAG(5879): getView #9 VT=1 CV=TextView text=> #### 
05-06 14:01:28.776 I/TAG(5879): getView #10 VT=1 CV=TextView text=> #### 
05-06 14:01:28.792 I/TAG(5879): getView #11 VT=1 CV=TextView text=> #### 
05-06 14:01:32.081 I/TAG(5879): getView #12 VT=1 CV=TextView text=> #### 
05-06 14:01:34.690 I/TAG(5879): getView #13 VT=1 CV=LinearLayout text=> #### 
05-06 14:01:35.573 I/TAG(5879): getView #14 VT=1 CV=TextView text=> #### 
05-06 14:01:37.237 I/TAG(5879): getView #15 VT=1 CV=TextView text=> #### 

正如你可能已经了解,我的布局l项目是TextViews,分隔符布局是一个LinearLayout。如您所见,一个“真实”物品(日志中的VT=1,请参阅物品#13)正在回收分隔物视图(CV=LinearLayout)。我原以为Android会提供convertView相同的类型,所以只有当滚动时必须创建相同类型的视图(即另一个分隔符)时才会回收第一个分隔符。

+0

你应该接受这些答案之一。 – 2013-05-08 14:00:06

+0

我知道,但直到现在我还没有。 – 2013-05-08 15:06:26

正如David发现的,这与Android框架有关。如here所述,该框架不期望Spinner具有不同的视图类型。

这是我用来做我的工作SpinnerAdapter因为我想解决方法:

  • 店在视图的标签视图类型;
  • 充气一个新的布局,如果没有视图转换OR如果当前视图类型从从转换视图不同。

这里是我的自定义getView方法的代码:

private View getView(final int layoutResId, final int position, final View convertView, final ViewGroup parent) { 
    View v; 

    if (convertView == null || (Integer)convertView.getTag() != getItemViewType(position)) { 
     final LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     v = li.inflate(layoutResId, parent, false); 
    } else { 
     v = convertView; 
    } 

    v.setTag(Integer.valueOf(getItemViewType(position))); 

    final TextView tv = (TextView) v.findViewById(mTextViewResId); 
    if (tv != null) { 
     tv.setText(getText(position)); 

     if (isSeparator(position)) { 
      tv.setOnClickListener(null); 
      tv.setOnTouchListener(null); 
     } 
    } 

    return v; 
} 
+0

+1 - 这个例子使我基本上,这些标签是无法覆盖'''getViewTypeCount''的。我会说这个答案可能会有更多的上下文抛出;执行getItemViewType ',被重写的'''getView'''(也许重写的'''getDropDownView''')等等。尼韦,谢谢! – 2015-10-01 20:07:33

的问题是在这里:

public View getView(final int position, final View convertView, final ViewGroup parent) { 
    return getView(mActionBarItemLayoutResId, position, convertView, parent); 
} 

此方法将总是返回View型,无论是呼吁建立一个分离器或一个数据项。您需要在此查看位置并返回适当的视图。

+0

这个'getView'是为'ActionBar'项目调用的,而不是下拉视图。此外,Android正在调用'getView'和'getDropDownView',以了解当前项目类型和循环查看项目类型。根据我的理解(和期望),Android应该保留两个(或'getViewTypeCount')视图池,当滚动将它们移出可见性时,它们将被回收。所以在调用'getDropDownView'时,Android应该选择相同类型的可回收视图,或者提供'null'。我对吗? – 2013-05-06 13:18:47

+0

是的,Android应该保留2个视图池并且正确地回收它们。不管怎样,这都是搞砸了。它变得困惑。我现在看到你已经覆盖'getItemId()'并且总是返回0.这可能是令人困惑的事情。尝试删除该方法,并删除重写的'hasStableIds()',看看是否有帮助。 – 2013-05-06 13:35:34

+4

啊,找到了。这是一个[Android错误](http://code.google.com/p/android/issues/detail?id=17128):-(微视图下拉视图不支持多视图回收。不便之处 – 2013-05-06 13:39:52