Android 中 ListView 的 setSelection 无效怎么办?

2019-09-30

关键字:ListView 自动选中


ListView 在手动选中选项的时候该选项是会高亮以示选中状态的。但有的时候我们往往会想在 ListView 一加载完成的时候即自动选中指定的某个选项,它虽然有提供一个 setSelection(int position) 方法,但遗憾的是该方法在 ListView 加载完成的时候并不会起作用。

这个方法不生效的具体原因我还没有去深究,只是感觉是因为 ListView 内部数据的准备是异步的,我们在初始化了 ListView 并给它设置好了 Adapter 以后,实际上 ListView 中的数据项是还没有加载出来的,你去让它选中一个选项,那自然是不得而终。

虽然我没有实际地去探索过这一现象的具体原因,但仍然还是找到两种“曲线救国”的办法来解决这一问题。特此记录一下,以期能帮助到有需要的同学。

这两种办法都依赖于我们要通过监听 ListView 的 OnItemClick 事件的方式来手动改变数据项的状态来实现。例如,我就在我的实现当中有这样一段代码调用:

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        if(adapterView.getTag() != null) {
            TextView oldTv = (TextView) adapterView.getTag();
            oldTv.setTextColor(ResourcesManager.getColor(R.color.text_color_black_333));
            oldTv.setBackgroundColor(ResourcesManager.getColor(android.R.color.transparent));
        }

        TextView curTv = (TextView) view;
        curTv.setTextColor(ResourcesManager.getColor(android.R.color.white));
        curTv.setBackgroundColor(ResourcesManager.getColor(R.color.basically_color));
        adapterView.setTag(curTv);
    }
知识兔

当 ListView 中的选项被点击以后,首先会从 adapterView 中将 getTag() 中的对象取出来。这里面的对象是什么呢?是我上一次设置了背景的选项的视图。这一 if 判断的目的是为了给原有的已选中的选项恢复成未选中样式。

然后就是将当前选中的样式,即形参 view 来设定自己需要的样式。设定完成后不要忘记调用一下 adapterView.setTag() 将这个 view 保存起来以便它的样式复原操作。

有的同学可能是直接通过在布局 xml 中通过样式 xml 来设置 ListView 选中时的展示样式的,这种方法我没有尝试过,就不在本篇文章的讨论范围之内了。

1、解决方案一

延时。

延时是一种比较简单粗暴的解决办法。它的核心思想就是在 ListView 初始化完毕后延时一段时间,然后再主动执行一下 onItemClick() 监听方法来实现。话不多说,直接上代码:

        lv.setAdapter(adapter);
        lv.setOnItemClickListener(this);
        lv.postDelayed(new Runnable() {
            @Override
            public void run() {
                int count = lv.getChildCount();
                if(count > 0) {
                    View v = lv.getChildAt(0);
                    onItemClick(lv, v, 0, 0);
                }
            }
        }, 2000);
        
知识兔

这里在 ListView 对象设置好 Adapter 和选项点击监听以后直接延时 2000 毫秒去执行“选中”操作。至于这个延时时长还得各位自己去把握。

当延时结束后,出于严谨,得首先判断一下 ListView 中是否已经加载出了足够的数据项,这个 ListView 自带的 getChildCount() 在这就很实用了。

当数据项数量足够以后,将我们需要的那一项的 View 取出来,作为参数传入 onItemClick() 方法中,就可以借由 onItemClick() 中定义好的样式设定代码来实现“自动选中”的功能了。

2、解决方案二

回调。

回调的核心思想与前面的 延时 一致,都是我们在条件满足的情况下主动调用 onItemClick() 来实现。只不过回调这一方式相比起 延时 来要精准得多,延时一来会造成视觉上的迟滞感,而且还有失败的风险,当延时结束后数据项仍未加载完,那我们的目的就达不到了。这两个问题在 回调 方式上都不是问题。

既然回调相比于延时要优秀这么多,那么它在实现上面自然是要复杂一些的,不过也仅仅是一些而已。

首先我们要回调的就是在我们需要默认选中的选项初始化完毕以后的时候。因此通常可以在自定义 Adapter 中定义这么一个回调接口并提供设置监听的方式,如:

public class MyAdapter extends ArrayAdapter<String> {

    private boolean isNotified;
    private Callback callback;

    public FunSettingArrayAdapter(Context context, int resource, String[] j, Callback callback) {
        super(context, resource, j);
        this.callback = callback;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v =  ...

        if(!isNotified && position == 0){
            isNotified = true;
            if(callback != null) {
                callback.onLoaded(v);
            }
        }

        return v;
    }

    interface Callback {
        /**
         * 主要用于设置 ListView 的自动选中功能。
         * */
        void onLoaded(View view);
    }
}
知识兔

上面代码中关键部分就是标红加粗部分。我这边的需求是要默认选中第 1 项,因此,在 position 为 0 时即触发回调。

而回调实现方法的做法与 延时 的做法大同小异:

MyAdapter adapter = new MyAdapter(inflater.getContext(), R.layout.layout,
        ResourcesManager.getResources().getStringArray(R.array.args), new MyAdapter.Callback() {
    @Override
    public void onLoaded(final View view) {
        lv.postDelayed(new Runnable() {
            @Override
            public void run() {
                onItemClick(lv, view, 0, 0);
            }
        }, 200);
    }
});
知识兔

这里需要注意,在前面自定义 Adapter 中,我们的回调实际上是发生在 getView() 返回 View 给 ListView 之前的,也就是说实际上我们收到回调通知是要比 ListView 收到对应  View 要早的。因此我们最好还是再做一个小延时来等待 ListView 加载我们所需要的数据项完毕为止。这里 200 毫秒已经很足够了。


计算机