起因
之前写过《Android使用反射机制设置ListView的默认焦点》,用反射来更改记录默认选中的那个变量,解决了一部分问题,可是并不能很好地解决所有的问题。
比如说GridView
,给它设置了监听器OnItemSelectedListener
,可是设置完后第一次并没有调用方法onItemSelected()
。(奇怪的是ListView可以)
我最近发现一个学习途径,可以很快举个例子说明,我们点击New -> Other -> Android (Android Sample Project),然后选择最新的版本,选择SupportDemos
,里面集成了该版本的所有Demo,是新手学习必备利器!
可以先运行看看效果!~里面都是一些Api的基本用法。
我在com.example.android.apis.view
包下找到了Grid1
,它是一个最简单的显示GridView
的Demo,代码如下
//Grid1.java
public 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.java
public 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.java
public 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
了,正是我需要的!