Spinner选择同一项无法没有回调的解决方法

对于Spinner,也是最近项目需求用的,才去了解这个控件。项目里面的出生年月日,一开始使用OS标准的DatePicker,但是后来客户想要这样的。

Spinner选择同一项无法没有回调的解决方法_第1张图片
Spinner.png

立马就想到了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());
        }
    }
}

以上就是两种解决方法。

你可能感兴趣的:(Spinner选择同一项无法没有回调的解决方法)