【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法
起因
之前写过《Android使用反射机制设置ListView的默认焦点》,用反射来更改记录默认选中的那个变量,解决了一部分问题,可是并不能很好地解决所有的问题。
比如说GridView
,给它设置了监听器OnItemSelectedListener
,可是设置完后第一次并没有调用方法onItemSelected()
。(奇怪的是ListView可以)
我最近发现一个学习途径,可以很快举个例子说明,我们点击New -> Other -> Android (Android Sample Project),然后选择最新的版本,选择SupportDemos
,里面集成了该版本的所有Demo,是新手学习必备利器!
可以先运行看看效果!~里面都是一些Api的基本用法。
我在com.example.android.apis.view
包下找到了Grid1
,它是一个最简单的显示GridView
的Demo,代码如下
//Grid1.javapublic class Grid1 extends Activity { GridView mGrid; @Override protected void onCreate(Bundle saveInstanceState) { super.onCreate(Bundle saveInstanceState); loadApps(); //do this in resume? setContentView(R.layout.grid_1); mGrid = (GridView) findViewById(R.id.myGrid); mGrid.setAdapter(new AppsAdapter()); } //其他略}
不得不说像发现宝藏一样发现这个学习途径,看上面的代码,间接把如何加载系统应用也学到了!~建议还不知道可以这样学习Demo的新手都尝试下!~
好,首先我们的目的是测试第一次选中是否会调用onItemSelected()
,我们在上面的代码后面加上监听器
//Grid1.javapublic class Grid1 extends Activity { GridView mGrid; @Override protected void onCreate(Bundle saveInstanceState) { //前面略 mGrid.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Log.d("azz", "position = " + position); } @Override public void onNothingSelected(AdapterView<?> parent) { Log.d("azz", "onNothingSelected"); } }); } //其他略}
运行结果是没有任何打印。可以看到,第一次打开应用,默认是选中0
位置的(有默认光标选中),但是并不会调用onItemSelected()
(奇怪的是,去Demo(List1
)给ListView
加上同样的代码第一次是可以调用的,虽然它们都是继承自AbsListView
,但表现却不太一样。好,这里先不讨论ListView
了)。
经过
于是我开始找源码,看是哪里负责调用onItemSelected()
方法。首先我们从setOnItemSelectedListener()
方法入手,Ctrl+鼠标左键进入,发现是父类AdapterView
的方法,于是我们在该父类中查找哪里调用了mOnItemSelectedListener.onItemSelected()
方法,找到了一个叫fireOnSelected()
的方法,而且只有这个方法里面有调用到。
//AdapterView.java private void fireOnSelected() { if (mOnItemSelectedListener == null) { return; } int selection = this.getSelectedItemPosition(); if (selection >= 0) { View v = getSelectedView(); mOnItemSelectedListener.onItemSelected(this, v, selection, getAdapter().getItemId(selection)); } else { mOnItemSelectedListener.onNothingSelected(this); } }
这个方法里面有个if/else
结构,说明如果调用了这个方法,是一定会调用mOnItemSelectedListener
的某个方法,而我们实际测试中发现并没有任何打印,说明fireOnSelected()
方法本身就没有被调用。
顺藤摸瓜,我们查一下究竟那里调用fireOnSelected()
方法,如下:
private class SelectionNotifier extends Handler implements Runnable { public void run() { if (mDataChanged) { // Data has changed between when this SelectionNotifier // was posted and now. We need to wait until the AdapterView // has been synched to the new data. post(this); } else { fireOnSelected(); } } } void selecionChanged() { if (mOnItemSelectedListener != null) { if (mInLayout || mBlockLayoutRequests) { // If we are in a layout traversal, defer notification // by posting. This ensures that view tree is // in a consistent state and is able to accomodate // new layout or invalidate requests. if (mSelectionNotifier == null) { mSelectionNotifier = new SelectionNotifier(); } mSelectionNotifier.post(mSelectionNotifier); } else { fireOnSelected(); } // we fire selection events here not in View if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } } }
可以看到一个是在线程SelectionNotifier
中,一个是在方法selecionChanged()
中。
根据上面英文猜测,在SelectionNotifier
中的意思是说当Adapter
绑定的数据发生变化时,要调用一次选中监听;
而selecionChanged()
好像更符合我们的需要的意思“当选中改变时调用选中监听”,而且我们在类中找到唯一调用该方法的地方为checkSelectionChanged()
void checkSelectionChanged() { if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { selectionChanged(); mOldSelectedPosition = mSelectedPosition; mOldSelectedRowId = mSelectedRowId; } }
看到这里可以猜想到为什么第一次不调用监听器了,就是因为if
中的条件不满足!于是我通过反射将mSelectedPosition
和mOldSelectedPosition
值打印出来,发现都是0
。
结果
其实我们的目的只是希望第一次fireOnSelected()
能够被调用就好了,因为它被调用了,选中监听必然被调用,所以我直接用反射运行这个方法,不就好了?
//Grid1.javapublic class Grid1 extends Activity { GridView mGrid; @Override protected void onCreate(Bundle saveInstanceState) { //前面略 mGrid.setOnItemSelectedListener(...); try { Method fireOnSelected = AdapterView.class.getDeclareMethod("fireOnSelected "); fireOnSelected.setAccessible(true); fireOnSelected.invoke(mGrid); //运行该方法 } catch (Exception e) { e.printStackTrace(); } } //其他略}
(发现我好喜欢用反射这种黑科技…)
然后运行,发现进入应用后,马上就有打印position = 0
了,正是我需要的!
更多相关文章
- 详解Android单元测试最佳实践
- 关于Android的线程问题
- Android将应用程序指定默认语言
- Android(安卓)APK反编译方法(可以获取APK xml和android Manifest,j
- 关于Android(安卓)studio的报错无法download某些文件的解决方法
- android与h5的互相交互传参
- Android入门进阶教程(16)-ActivityThead、ActivityManagerServic
- Android中Service和Activity相互通信示例代码
- Android(安卓)LayoutInflater原理解析