美文网首页
旧问新解·ListView 中的 OnItemSelectedL

旧问新解·ListView 中的 OnItemSelectedL

作者: 幺鹿 | 来源:发表于2017-03-10 15:41 被阅读1221次

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;
    }

相关文章

  • 旧问新解·ListView 中的 OnItemSelectedL

    1、概述 今天在写颜色识别的Demo 时有个场景是需要用户做出单项选择,脑中蹦出首选的方案就是 ListView ...

  • 旧语新解

    不到黄河不死心,不撞南墙不回头。 到了黄河也不死心,搭个桥过去,或者趟水过去。撞了南墙也不回头,找个梯子爬上去翻过去。

  • 旧词新解

    存款【貯金 我挺喜欢存钱。大学的时候用从小到大存的压岁钱作为首付买了一套一室一厅出租(租金抵消月供)。最近对存款这...

  • 旧词新解

    晏殊《浣溪沙》 “一曲新词酒一杯,去年天气旧亭台。夕阳西下几时回?” 坐于堂内,一茶一书,安然若素。春节临近,寒假...

  • Flutter ListView嵌套ListView

    ListView嵌套ListView 在实际开发中,我们经常碰到ListView中嵌套ListView 实例代码分...

  • flutter遇到的坑

    Flutter中ListView嵌套GridView、ListView嵌套ListView

  • Promise知识点总结

    Promise 是什么? Promise是ES6语法,是JS中解决异步编程的新解决方案。(旧的解决方案是单纯的调用...

  • Flutter之ListView、GridView

    ListView ListView属性代码,与Android中ListView功能一样的列表控件。 示例中,在Li...

  • ScrollView嵌套ListView问题

    四种方案解决ScrollView嵌套ListView问题 “四种方案解决ScrollView嵌套ListView问...

  • Flutter进阶:深入探究 ListView 和 Scroll

    Flutter 中的 ListView 可以对比 Android 中的 ListView 或者 RecycleVi...

网友评论

      本文标题:旧问新解·ListView 中的 OnItemSelectedL

      本文链接:https://www.haomeiwen.com/subject/nnpugttx.html