旧问新解·ListView 中的 OnItemSelectedListener 不生效

1、概述

今天在写颜色识别的Demo 时有个场景是需要用户做出单项选择,脑中蹦出首选的方案就是 ListView 配合 ChoiceMode。

但实际在编写过程中却出了问题 :ListView 中的 OnItemSelectedListener 没有从 ListView 中接收回调。出现问题并不可怕,可怕的是对问题视而不见的态度。

2、解决问题

2.1、OnItemSelectedListener的定义

OnItemSelectedListener 是当视图被选中时会触发的回调。

  • onItemSelected 只有当状态与前一个状态不同时,才会触发。
  • onNothingSelected 当视图不可见或者数据源为空时会触发该方法。
public abstract class AdapterView<T extends Adapter> extends ViewGroup {
        public interface OnItemSelectedListener {
                void onItemSelected(AdapterView<?> parent, View view, int position, long id);
                void onNothingSelected(AdapterView<?> parent);
        }
}

2.2、设置 OnItemSelectedListener

通过调用setOnItemSelectedListener()方法为mOnItemSelectedListener初始化。通过查询onItemSelected()方法的调用,追踪到下面方法。

// AdapterView
    private void fireOnSelected() {
        if (mOnItemSelectedListener == null) {
            return;
        }
        final int selection = getSelectedItemPosition();
        if (selection >= 0) {
            View v = getSelectedView();
            mOnItemSelectedListener.onItemSelected(this, v, selection,
                    getAdapter().getItemId(selection));
        } else {
            mOnItemSelectedListener.onNothingSelected(this);
        }
    }

最终是由fireOnSelected()方法封装了对事件的回调,接着查到是dispatchOnItemSelected ()中调用了fireOnSelected()方法。

// AdapterView
    private void dispatchOnItemSelected() {
        fireOnSelected();
        performAccessibilityActionsOnSelected();
    }

接着跟踪dispatchOnItemSelected()方法,查找到两处使用场景。

// 使用场景1
    private class SelectionNotifier implements Runnable {
        public void run() {
            mPendingSelectionNotifier = null;

            if (mDataChanged && getViewRootImpl() != null
                    && getViewRootImpl().isLayoutRequested()) {
                if (getAdapter() != null) {
                    mPendingSelectionNotifier = this;
                }
            } else {
                dispatchOnItemSelected();
            }
        }
    }
// 使用场景2
    void selectionChanged() {
        mPendingSelectionNotifier = null;

        if (mOnItemSelectedListener != null
                || AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mInLayout || mBlockLayoutRequests) {
                if (mSelectionNotifier == null) {
                    mSelectionNotifier = new SelectionNotifier();
                } else {
                    removeCallbacks(mSelectionNotifier);
                }
                post(mSelectionNotifier);
            } else {
                dispatchOnItemSelected();
            }
        }
    }

2.2.1、使用场景 SelectionNotifier

因为类SelectionNotifier是私有访问权限,所以只需要在当前的类(AdapterView)中查找即可。最终发现SelectionNotifier的创建竟然在selectionChanged()方法中,所以我们可以直接对下一个场景展开分析了。

2.2.2、使用场景 selectionChanged()

通过查找方法selectionChanged()调用,我们定位到方法checkSelectionChanged()

    void checkSelectionChanged() {
        if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
            selectionChanged();
            mOldSelectedPosition = mSelectedPosition;
            mOldSelectedRowId = mSelectedRowId;
        }
        if (mPendingSelectionNotifier != null) {
            mPendingSelectionNotifier.run();
        }
    }

继续对方法checkSelectionChanged()展开搜索,定位到下面代码块。handleDataChanged()的代码比较多,就不贴出代码了。

// AdapterView
void handleDataChanged() {
      // 1、获取待新选中的位置
      // 2、如果选中的位置与之前的位置是同一个位置,就不触发视图更新了
      // 3、如果选中的位置与之前不一样,设置当前位置为新的位置。并通知分发位置改变事件。
      // 4、如果发现没有选中位置的匹配项,则会分发一个未选择的事件。
}

3、4都会最终触发checkSelectionChanged()事件,所以问题的关键变成了谁调用了handleDataChanged()方法。

2.3、追踪handleDataChanged()方法

我们在 ListView 中找到了对handleDataChanged()方法的调用,我们发现两条线索触发了对handleDataChanged()方法的调用。

1、layoutChildren() 方法的调用
2、mDataChanged的取值

@Override
protected void layoutChildren() {
//  ...

boolean dataChanged = mDataChanged;
            if (dataChanged) {
                handleDataChanged();
            }
// ...
}

2.3.1、layoutChildren() 方法的调用

layoutChildren() 的方法调用

我们观察到选中的三个方法会调用了layoutChildren() 方法,这三个方法分别是:
1、setSelectionInt() —— 最终被commonKey()调用
2、commonKey() —— 最终被onKeyMultiple()调用
3、onFocusChanged()

黑人问号脸

在 ListView 中layoutChildren() 方法 与 点击事件 扯不上半点关系,反而跟 Focus 纠缠不清。

2.3.2、mDataChanged的取值

当数据变更或者失效的时候都会引起mDataChanged值的变更。

// AdapterView
class AdapterDataSetObserver extends DataSetObserver {
        // ...
        @Override
        public void onChanged() {
            mDataChanged = true;
        }
        // ...
        @Override
        public void onInvalidated() {
            mDataChanged = true;
         // ...
        }
}

总结

在 ListView 中点击 Item 并没有调用 OnItemSelectListener回调,所以最开始期望以注册OnItemSelectListener来接收点击 Item 的回调行为是不成立的。

至少在ListView中 OnItemSelectListener是用于接收焦点的变化的。

解决办法

1、在 ChoiceMode 是 CHOICE_MODE_SINGLE的情况下,你可以选择使用方法getCheckedItemPosition()

public int getCheckedItemPosition() {
        if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
            return mCheckStates.keyAt(0);
        }

        return INVALID_POSITION;
    }

如果是其他 ChoideMode,则可以选择getCheckedItemPositions()方法。

    public SparseBooleanArray getCheckedItemPositions() {
        if (mChoiceMode != CHOICE_MODE_NONE) {
            return mCheckStates;
        }
        return null;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,339评论 0 15
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • java 接口的意义-百度 规范、扩展、回调 抽象类的意义-乐视 为其子类提供一个公共的类型封装子类中得重复内容定...
    交流电1582阅读 2,147评论 0 11
  • 有时候思念总是蔓延 有时候泪珠总是断线 有时候你我总是无言 有时候我们孤单眷恋 你的脸你的好看的侧脸 我总是深深想...
    林安雪阅读 190评论 0 1