使用DataBinding绑定SearchableSpinner时设置Adapter触发类型转换异常
咱们来捋捋这个问题的核心矛盾:
当你用DataBinding的android:entries="@{event.internalCategoryNames}"绑定ObservableList时,DataBinding会自动生成一个ObservableListAdapter来适配数据,但这个类是DataBinding内部的包私有类,而且它和ArrayAdapter是SpinnerAdapter的平级子类,没有继承关系。而你用的SearchableSpinner在setAdapter方法里硬把传入的SpinnerAdapter强转成了ArrayAdapter,这就直接触发了ClassCastException。
给你几个可行的解决路子:
路子一:修改SearchableSpinner的源码(最彻底)
既然SearchableSpinner依赖ArrayAdapter的功能,咱们得调整它的setAdapter逻辑,兼容ObservableListAdapter的情况:
@Override public void setAdapter(SpinnerAdapter adapter) { if (!_isFromInit) { // 先判断adapter类型,避免直接强转 if (adapter instanceof ArrayAdapter) { _arrayAdapter = (ArrayAdapter) adapter; } else { // 处理DataBinding生成的ObservableListAdapter List<?> dataList = null; // 因为ObservableListAdapter是包私有,只能通过反射取内部的数据源 try { Field listField = adapter.getClass().getDeclaredField("mList"); listField.setAccessible(true); dataList = (List<?>) listField.get(adapter); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } if (dataList != null) { // 把数据源包装成ArrayAdapter _arrayAdapter = new ArrayAdapter<>(_context, android.R.layout.simple_list_item_1, dataList); } else { throw new IllegalArgumentException("Unsupported adapter type for SearchableSpinner"); } } // 保留原有的hint逻辑 if (!TextUtils.isEmpty(_strHintText) && !_isDirty) { ArrayAdapter<String> hintAdapter = new ArrayAdapter<>(_context, android.R.layout.simple_list_item_1, new String[]{_strHintText}); super.setAdapter(hintAdapter); } else { super.setAdapter(_arrayAdapter); } } else { _isFromInit = false; super.setAdapter(adapter); } }
不过反射可能存在版本兼容风险(比如DataBinding内部结构变化),如果不想用反射,可以试试下面的方法。
路子二:手动创建ArrayAdapter,绕过DataBinding自动生成逻辑
直接在你的ViewModel(比如Event类)里把列表数据包装成ArrayAdapter,然后通过DataBinding绑定给Spinner:
- 修改布局,去掉
android:entries,换成自定义的adapter绑定:
<com.yourpackage.SearchableSpinner android:id="@+id/add_event_internal_category_spinner" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginTop="@dimen/default_margin" android:prompt="@string/spinner_category_title" android:selectedItemPosition="@={event.selectedInternalCategoryPosition}" android:visibility="@{event.editable?View.VISIBLE:View.GONE}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView6" app:adapter="@{event.internalCategoryAdapter}" />
- 在
Event类里添加internalCategoryAdapter属性:
private ArrayAdapter<String> internalCategoryAdapter; private Context context; // 记得初始化Context,比如通过构造传入 public ArrayAdapter<String> getInternalCategoryAdapter() { if (internalCategoryAdapter == null && internalCategoryNames != null) { internalCategoryAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, internalCategoryNames); internalCategoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); } return internalCategoryAdapter; }
这样DataBinding会直接把你创建的ArrayAdapter传给SearchableSpinner,完全避开类型转换问题。
路子三:写自定义DataBinding BindingAdapter
如果你不想改第三方库源码,也不想手动管理Adapter,可以写一个自定义的BindingAdapter,专门处理SearchableSpinner的entries绑定:
@BindingAdapter("entries") public static void setSearchableSpinnerEntries(SearchableSpinner spinner, List<String> entries) { if (entries != null) { ArrayAdapter<String> adapter = new ArrayAdapter<>(spinner.getContext(), android.R.layout.simple_spinner_item, entries); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); } }
布局里直接用app:entries="@{event.internalCategoryNames}"(注意用app命名空间),DataBinding就会调用这个自定义方法,生成兼容的ArrayAdapter。
个人更推荐路子二,代码直观、易维护,也不用依赖反射或者修改第三方库。
内容的提问来源于stack exchange,提问作者user1209216

