起因

之前写过《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中的条件不满足!于是我通过反射将mSelectedPositionmOldSelectedPosition值打印出来,发现都是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了,正是我需要的!

更多相关文章

  1. 详解Android单元测试最佳实践
  2. 关于Android的线程问题
  3. Android将应用程序指定默认语言
  4. Android(安卓)APK反编译方法(可以获取APK xml和android Manifest,j
  5. 关于Android(安卓)studio的报错无法download某些文件的解决方法
  6. android与h5的互相交互传参
  7. Android入门进阶教程(16)-ActivityThead、ActivityManagerServic
  8. Android中Service和Activity相互通信示例代码
  9. Android(安卓)LayoutInflater原理解析

随机推荐

  1. Java和MySQL编写的简单手机销售管理系统
  2. 带有条件的MySQL中Row的值的总和
  3. mysql python pymysql模块 增删改查 查询
  4. Ruby on Rails SQL查询返回#<ActiveRecord
  5. Linux下修改MySQL初始密码、开启远程登录
  6. Ubuntu下安装MySQL并实现远程登录?
  7. mysql执行update语句时报错:Data truncati
  8. 在Python中使用AWS Lambda使用MySQL时出
  9. Laravel 4中的多选过滤搜索
  10. php使用mysql数据库时中文不显示或显示异