对于Spinner,也是最近项目需求用的,才去了解这个控件。项目里面的出生年月日,一开始使用OS标准的DatePicker,但是后来客户想要这样的。
立马就想到了Spinner,于是就开始着手,具体的代码就不贴了。
想要解决的是选择同一项的时候,也能回调
void onItemSelected(AdapterView> parent, View view, int position, long id)
方法。先看下为什么不能回调?
AdapterView.class源码
/**
* Register a callback to be invoked when an item in this AdapterView has
* been selected.
*
* @param listener The callback that will run
*/
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
}
// 然后看下 mOnItemSelectedListener 这个监听对象在哪儿调用onItemSelected方法
// 也就触发了onItemSelected事件。
private void fireOnSelected() {
if (mOnItemSelectedListener == null) {
return;
}
final int selection = getSelectedItemPosition();
if (selection >= 0) {
View v = getSelectedView();
mOnItemSelectedListener.onItemSelected(this, v, selection,
getAdapter().getItemId(selection));
} else {
mOnItemSelectedListener.onNothingSelected(this);
}
}
// 那在看看是在哪里调用fireOnSelected()方法
private void dispatchOnItemSelected() {
fireOnSelected();
performAccessibilityActionsOnSelected();
}
void selectionChanged() {
// We're about to post or run the selection notifier, so we don't need
// a pending notifier.
mPendingSelectionNotifier = null;
if (mOnItemSelectedListener != null
|| AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mInLayout || mBlockLayoutRequests) {
// If we are in a layout traversal, defer notification
// by posting. This ensures that the view tree is
// in a consistent state and is able to accommodate
// new layout or invalidate requests.
if (mSelectionNotifier == null) {
mSelectionNotifier = new SelectionNotifier();
} else {
removeCallbacks(mSelectionNotifier);
}
post(mSelectionNotifier);
} else {
dispatchOnItemSelected();
}
}
// Always notify AutoFillManager - it will return right away if autofill is disabled.
final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
if (afm != null) {
afm.notifyValueChanged(this);
}
}
/**
* Called after layout to determine whether the selection position needs to
* be updated. Also used to fire any pending selection events.
*/
void checkSelectionChanged() {
if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
selectionChanged();
mOldSelectedPosition = mSelectedPosition;
mOldSelectedRowId = mSelectedRowId;
}
// If we have a pending selection notification -- and we won't if we
// just fired one in selectionChanged() -- run it now.
if (mPendingSelectionNotifier != null) {
mPendingSelectionNotifier.run();
}
}
我们看这个判断条件
if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId))
,比较当前选项的位置与上一次选择的位置是否不相同,如果不相同就可以调用selectionChanged()执行onItemSelected事件。这就是选择一项时,必须与上一次选项的位置不相同才可以触发选择事件。
那该如何解决?
方法1:
利用反射的方式修改上一次位置的值,只要与当前选择的位置不同就可以出发事件了。因为mOldSelectedPosition是私有属性,所以我们需要使用反射来修改mOldSelectedPosition的值。代码如下:
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id) {
try {
Field field = AdapterView.class.getDeclaredField("mOldSelectedPosition");
field.setAccessible(true); //设置mOldSelectedPosition可访问
field.setInt(spinner, AdapterView.INVALID_POSITION); //设置mOldSelectedPosition的值
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onNothingSelected(AdapterView> parent) {
}
如果页面只有一个Spinner是没有问题的,但是当页面包含了两个及以上的Spinner的时候就会出现一个问题:当点击
第一个
spinner的item时候,会触发其他Spinner的listener的onItemSelected
方法,这就比较难受了,明明是只操作一个,但是其他也有响应。
方法2
在原有Spinner基础上自定义,代码不多
public class CKDateSpinner extends AppCompatSpinner {
public CKDateSpinner(Context context) {
super(context);
}
public CKDateSpinner(Context context, int mode) {
super(context, mode);
}
public CKDateSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setSelection(int position, boolean animate) {
boolean sameSelected = position == getSelectedItemPosition();
super.setSelection(position, animate);
if (sameSelected) {
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
}
}
@Override
public void setSelection(int position) {
boolean sameSelected = position == getSelectedItemPosition();
super.setSelection(position);
if (sameSelected) {
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
}
}
}
以上就是两种解决方法。