【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法

时间:2021-04-24 16:14:40

起因

之前写过《Android使用反射机制设置ListView的默认焦点》,用反射来更改记录默认选中的那个变量,解决了一部分问题,可是并不能很好地解决所有的问题。

比如说GridView,给它设置了监听器OnItemSelectedListener,可是设置完后第一次并没有调用方法onItemSelected()。(奇怪的是ListView可以)

我最近发现一个学习途径,可以很快举个例子说明,我们点击New -> Other -> Android (Android Sample Project),然后选择最新的版本,选择SupportDemos,里面集成了该版本的所有Demo,是新手学习必备利器!

【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法
【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法
【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法
【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法
【Android解决方案】GridView第一次选中不调用onItemSelected()的解决办法

可以先运行看看效果!~里面都是一些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中的条件不满足!于是我通过反射将mSelectedPositionmOldSelectedPosition值打印出来,发现都是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了,正是我需要的!