Android中Spinner控件关于二次点击同一item无响应事件解析及处理方法
来源:互联网 发布:淘宝网上有卖烟的吗 编辑:程序博客网 时间:2024/04/29 23:39
分析
在Android开发中难免会使用到Spinner控件,而且经常会对其绑定点击事件。下面就从源码上来解析下为什么Spinner不对同一item二次点击进行事件响应。
我们从Spinner的事件入手,我们来看以下几个事件绑定,首先是Spinner本身的
/** * <p>A spinner does not support item click events. Calling this method * will raise an exception.</p> * <p>Instead use {@link AdapterView#setOnItemSelectedListener}. * * @param l this listener will be ignored */ @Override public void setOnItemClickListener(OnItemClickListener l) { throw new RuntimeException("setOnItemClickListener cannot be used with a spinner."); }
好吧,显然 这个方法是无用的。再看Spinner的父类AbsSpinner发现其中并没有关于事件绑定的方法,继续往上找AbsSpinner的父类AdapterView,我们可以发现以下几个与点击事件相关的方法:
- setOnClickListener(View.OnClickListener l)
- setOnItemClickListener(AdapterView.OnItemClickListener listener)
- setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)
setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener)
由于Spinner本身override了setOnItemClickListener()方法 所以这个略过,那么剩下的
@Override public void setOnClickListener(OnClickListener l) { throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " + "You probably want setOnItemClickListener instead"); }
setOnClickListener()这个也不用看了,继续
/** * Register a callback to be invoked when an item in this AdapterView has * been clicked and held * * @param listener The callback that will run */ public void setOnItemLongClickListener(OnItemLongClickListener listener) { if (!isLongClickable()) { setLongClickable(true); } mOnItemLongClickListener = listener; }
这个顾名思义长点击事件,这个不符合我们使用情况,最后只剩下一个方法也是我们平时用的
/** * 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; } /** * Interface definition for a callback to be invoked when * an item in this view has been selected. */ public interface OnItemSelectedListener { /** * <p>Callback method to be invoked when an item in this view has been * selected. This callback is invoked only when the newly selected * position is different from the previously selected position or if * there was no selected item.</p> * * Impelmenters can call getItemAtPosition(position) if they need to access the * data associated with the selected item. * * @param parent The AdapterView where the selection happened * @param view The view within the AdapterView that was clicked * @param position The position of the view in the adapter * @param id The row id of the item that is selected */ void onItemSelected(AdapterView<?> parent, View view, int position, long id); /** * Callback method to be invoked when the selection disappears from this * view. The selection can disappear for instance when touch is activated * or when the adapter becomes empty. * * @param parent The AdapterView that now contains no selected item. */ void onNothingSelected(AdapterView<?> parent); }
从onItemSelected的注释中可知google设计Spinner只对有别于之前选中的选中项进行事件响应,那么Spinner是如何触发选中事件呢?
/** * 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(); } } 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(); } } }
我们可以找到上面的方法、通过注释可知这个方法是在layout确定是否需要对选中位置进行更新操作后调用的,同样常被用来触发任何待定(未发送)的选择事件。
那么选中事件是否响应取决于以下条件
(mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)
这个条件的意思就是:当前选择的跟之前选中的不一样的才会触发selectionChanged()方法来
post(mSelectionNotifier);那么解决方法来了,只要让以上条件保持成立就可以了即保证mOldSelectedPosition或者mOldSelectedRowId不为当前选中值,最简单的就是把他们改成初始值,那么这里我们就需要使用放射的机制来达到这个目的。
方法一
示例如下
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { try { Class<?> clazz = AdapterView.class; Field field = clazz.getDeclaredField("mOldSelectedPosition"); field.setAccessible(true); field.setInt(mSpinner,AdapterView.INVALID_POSITION); } catch(Exception e){ e.printStackTrace(); } } @Override public void onNothingSelected(AdapterView<?> parent) { System.out.println(parent.getId()); } });
当然你也可以在onTouch事件里对值进行修改:
mSpinner.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { try { Class<?> clazz = AdapterView.class; Field field = clazz.getDeclaredField("mOldSelectedPosition"); field.setAccessible(true); field.setInt(mSpinner,AdapterView.INVALID_POSITION); } catch(Exception e){ e.printStackTrace(); } return false; } });
方法二
不可厚非,通过以上方法是可以达到目的,但是未免太过于暴力,那么是否还有其他方法呢?回到checkSelectionChanged() 的注释看看:
Called after layout to determine whether the selection position needs to
be updated. Also used to fire any pending selection events.
从这里可以得知一个信息,在选中item时会触发layout更新事件,那么从这里能不能获得我们想到的东西,我们继续往上找到AdapterView的父类ViewGroup。我们可以找到以下方法:
/** * Interface definition for a callback to be invoked when the hierarchy * within this view changed. The hierarchy changes whenever a child is added * to or removed from this view. */ public interface OnHierarchyChangeListener { /** * Called when a new child is added to a parent view. * * @param parent the view in which a child was added * @param child the new child view added in the hierarchy */ void onChildViewAdded(View parent, View child); /** * Called when a child is removed from a parent view. * * @param parent the view from which the child was removed * @param child the child removed from the hierarchy */ void onChildViewRemoved(View parent, View child); } /** * Register a callback to be invoked when a child is added to or removed * from this view. * * @param listener the callback to invoke on hierarchy change */ public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { mOnHierarchyChangeListener = listener; }
那么我们能不能通过判断子view的差异来判断是否为二次点击同一item(view)事件。当Spinner点击item时触发这个监听,有以下两种情况:
- 前后点击不同item
监听回调的顺序是:onChildViewRemoved{从父类view中移除选中状态的item_A视图并使之处于分离状态}–>onChildViewAdded{往父类view新增选中状态的item_B视图}–>onChildViewRemoved{完成对处于分离状态的item_A视图移除} - 前后点击同一个item
监听回调的顺序是:onChildViewRemoved{选中状态的item_A视图}–>onChildViewAdded{选中状态的item_A视图}
那么我们通过对onChildViewRemoved()方法进行断点调试可以在parent里看到以下内容:
可以很明显的看到mNextSelectedRowId、mOldSelectedRowId、mSelectedRowId、mOldSelectedPosition、mSelectedPosition这几个变量,那么只要取到这前后的两个选中项id就可以满足我们的需求了、因为不管是那种情况onChildViewRemoved是一定会被执行的,相对上一个方法会更温和些。示例如下:
mSpinner.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { } @Override public void onChildViewRemoved(View parent, View child) { try { Class<?> clazz = AdapterView.class; Field mOldSelectedPosition = clazz.getDeclaredField("mOldSelectedPosition"); Field mSelectedPosition = clazz.getDeclaredField("mSelectedPosition"); mOldSelectedPosition.setAccessible(true); mSelectedPosition.setAccessible(true); if (mOldSelectedPosition.getInt(mSpinner) == mSelectedPosition.getInt(mSpinner)) { //响应事件 System.out.println("mSelectedPosition" + mSelectedPosition.getInt(mSpinner)); } } catch (Exception e) { e.printStackTrace(); } } });
以上就是两种处理Spinner二次点击同一Item无响应解决方案。如果哪位网友有更好的方法,麻烦在评论里告知,谢谢!
- Android中Spinner控件关于二次点击同一item无响应事件解析及处理方法
- android abslistview item点击事件无响应的处理方式
- Spinner同一Item事件响应+默认第一次不触发事件
- Android中设置Listview的item之间透明及item点击无响应问题
- 解决android ListView item中事件处理无法响应方法
- Android中Fragment点击事件的添加(及点击事件无响应的原因)
- 关于ListView中点击列表中的item无响应问题
- ListView的Item点击事件无响应
- Android--焦点问题-ListView中item及其子控件无法响应点击事件
- listview 点击item无响应处理
- Android spinner点击相同选项处理无法响应事件问题,自定义spinner
- Android中ListView(gridview)的item中有button等子点击控件时不能响应点击事件的原因
- Android中ListView响应Item内部点击事件
- Android如何处理列表控件的item同时点击事件
- Android spinner点击相同选项处理无法响应事件问题,暴力反射
- ListView的item点击事件无响应的解决方法
- Android关于listview item无响应
- 关于ListView中控件点击事件与Item点击事件冲突的问题
- linux偶发性崩溃的程序该怎么调试 coredump gdb
- calculate a point in triangle
- hdu1179Ollivanders: Makers of Fine Wands since 382 BC.(二分匹配)
- 使用cocoapods 中的use_frameworks! 和.h文件冲突
- Oracle 11g新特性:SQL Query Result Cache
- Android中Spinner控件关于二次点击同一item无响应事件解析及处理方法
- XposedHook:hook敏感函数
- 基数、选择性、直方图
- 二叉树的二叉链表存储结构构建以及先序遍历
- Linux Core Dump
- Linux core dump详解
- 【kaldi】VMware12+Ubuntu16.04+kaldi安装遇到的问题
- 图的邻接矩阵表示与最短路径算法( Dijkstra )代码实现
- Phaser.js音频资源处理篇