I'm trying to make an ActionBar spinner that has separators. I have implemented a SpinnerAdapter
that has 2 item view types (thanks to getViewTypeCount
). The problem is that I'm being sent some convertViews
from the other type.
Here's my 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;
}
}
One implementation:
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);
}
}
As you may have noticed, I have set a log line into getView()
so that I better understand what's going on:
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=> ####
As you may have understood, my layouts for the real items are TextViews, and the separator layout is a LinearLayout.
As you can see, a "real" item (VT=1
in the log, see item #13) is being recycling a separator view (CV=LinearLayout
). I would have thought that Android would provide a convertView
of the same type, so the first separator would be recycled only if a view of the same type (i.e. another separator) would have to be created when scrolling.
As David found out, this is related to the Android framework. As noted here, the framework doesn't expect Spinner
s to have different view types.
This is the workaround I used to make my SpinnerAdapter work as I wanted:
Here's the code of my custom getView
method:
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;
}
The problem is here:
public View getView(final int position, final View convertView, final ViewGroup parent) {
return getView(mActionBarItemLayoutResId, position, convertView, parent);
}
This method will always return the same View
type, whether called for a separator or a data item. You need to check the position here and return an appropriate view.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With