Spinner: 当所选项未改变时,onItemSelected不被调用

56
我有一个 SpinnerOnItemSelectedListener,但当所选项与先前选择的相同时,它不会被调用。显然,OnClickListener 对于 Spinner 不是可选项。我需要捕获每次用户单击项的情况。有什么想法吗?也许这个 SpinnerActionBar 中的事实会影响正常的行为?
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.tracklist_menu, menu);
    Spinner spinner = (Spinner) menu.findItem(R.id.option_ordering_spinner)
            .getActionView();
    spinner.setAdapter(mSpinnerAdapter);
    spinner.setSelection(PrefsHelper.getOrderingSpinnerPos(prefs));
    spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view,
                int position, long id) {
            String str = "selected";
            System.out.println(str);
            if (optionMenuInitialized) {

                switch (position) {
                case 0:
                    // rdm
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_RESHUFFLE_PLAYLIST));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                case 1:
                    // artist
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_ORDER_PLAYLIST_BY_ARTIST));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                case 2:
                    // folder
                    getActivity()
                            .sendBroadcast(
                                    new Intent(
                                            MyIntentAction.DO_ORDER_PLAYLIST_BY_FOLDER));
                    smp.setCurrentTracklistCursorPos(-1);
                    trackAdapter.notifyDataSetChanged();
                    break;
                }
                PrefsHelper.setOrderingSpinnerPos(prefEditor, position);
                prefEditor.commit();
            }
            optionMenuInitialized = true;
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });
}

1
你读过这篇文章吗? https://dev59.com/WG865IYBdhLWcg3wNL07 - Zain Ali
是的,那并没有帮助到我... 不管怎样,我刚刚找到了解决方案,现在要在这里写下来 =) - elgui
这与此无关,但将Spinner的可见性设置为VIEW.GONE也会导致此问题。 - Megha Maniar
11个回答

90

好的,我最终找到了一个解决方案,通过创建自己的类扩展Spinner:

public class MySpinner extends Spinner {
OnItemSelectedListener listener;

public MySpinner(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public void setSelection(int position) {
    super.setSelection(position);
    if (listener != null)
        listener.onItemSelected(null, null, position, 0);
}

public void setOnItemSelectedEvenIfUnchangedListener(
        OnItemSelectedListener listener) {
    this.listener = listener;
}
}

3
能否分享一些如何实现 ActionBar 导航列表相同功能的想法? - Alex Semeniuk
@dhams 抱歉,我在回答中犯了一个错误,已经将其删除。 - benoffi7
@AlexSemeniuk 对于想要在ActionBar下拉菜单中重新选择项目的所有人,我找到了一个小解决方案:http://stackoverflow.com/a/22961995/2633814 - blackfizz
1
如何在listener.onItemSelected(null, null, position, 0);中获取父级和视图? - Jesus Almaral - Hackaprende
@elgui,我该如何调用这个类。请帮帮我。 - Bala Raja
显示剩余7条评论

26

我发现这份工作与所提供的那份不同

/** Spinner extension that calls onItemSelected even when the selection is the same as its previous value */
public class NDSpinner extends Spinner {

  public NDSpinner(Context context)
  { super(context); }

  public NDSpinner(Context context, AttributeSet attrs)
  { super(context, attrs); }

  public NDSpinner(Context context, AttributeSet attrs, int defStyle)
  { super(context, attrs, defStyle); }

  @Override public void
  setSelection(int position, boolean animate)
  {
    boolean sameSelected = position == getSelectedItemPosition();
    super.setSelection(position, animate);
    if (sameSelected) {
      // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
      getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
    }
  }

  @Override public void
  setSelection(int position)
  {
    boolean sameSelected = position == getSelectedItemPosition();
    super.setSelection(position);
    if (sameSelected) {
      // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
      getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
    }
  }
}

1
如果没有设置OnItemSelectedListener并且选择了相同的项目,您将会收到NPE。 - Andrey Makarov
4
这个对我也有效,被接受的答案却没有用。 - Analizer
为了给原作者以荣誉:https://dev59.com/1G435IYBdhLWcg3wlBCC#11323043 - Raimund Krämer

6
为了使您的旋转器在选择的最后一个索引值改变时也发生变化,只需使用以下代码:
spinner.setSelection(0); 

在其他选择之前被调用。
spinner.setSelection(number); 

这样做会触发两次“OnItemSelected”事件,只需确保第二次执行所需操作即可。

1
如果同一项的位置为0怎么办? - V-rund Puro-hit
spinner.setSelection(0); 如果(number!=0) spinner.setSelection(number); - Ramiro G.M.

6

将常见的解决方案重写,但考虑了以下几点:

  1. 使用androidx
  2. AppCompatSpinner扩展
  3. 使用内置的OnItemSelectedListener监听器,而不是创建自己的监听器
  4. 添加了初始监听器调用hack

这里:

import android.content.Context;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatSpinner;


public class FixedSpinner extends AppCompatSpinner {
    // add other constructors that you need
    public FixedSpinner(Context context, int mode) {
        super(context, mode);
    }

    private void processSelection(int position) {
        boolean sameSelected = position == getSelectedItemPosition();
        final OnItemSelectedListener listener = getOnItemSelectedListener();
        if (sameSelected && listener != null) {
            // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
            listener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
    }

    @Override
    public void setSelection(int position) {
        processSelection(position);
        super.setSelection(position);
    }

    @Override
    public void setSelection(int position, boolean animate) {
        processSelection(position);
        super.setSelection(position, animate);
    }

    @Override
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        // This hack fixes bug, when immediately after initialization, listener is called
        // To make this fix work, first add data, only then set listener
        // Having done this, you may refresh adapter data as many times as you want
        setSelection(0, false);
        super.setOnItemSelectedListener(listener);
    }
}

太棒了,谢谢。这解决了我在 spinner 上遇到的问题。 - Saenic
布尔值sameSelected = position == getSelectedItemPosition();始终为真,getSelectedItemPosition()方法返回的值与position相同。 - Vishal Yadav

6

这里是一个更好的实现:

public class SpinnerPlus extends Spinner {
    AdapterView.OnItemSelectedListener listener;

    public SpinnerPlus(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        if (listener != null)
            listener.onItemSelected(this, getSelectedView(), position, 0);
    }

    public void setOnItemSelectedEvenIfUnchangedListener(
            AdapterView.OnItemSelectedListener listener) {
        this.listener = listener;
    }
}

5

这里是更好的实现方式 -

自定义Spinner类 -

import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatSpinner;

public class CSpinner extends AppCompatSpinner {

    private int lastPosition = 0;

    public CSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        boolean sameSelected = lastPosition == getSelectedItemPosition();
        OnItemSelectedListener onItemSelectedListener = getOnItemSelectedListener();
        if (sameSelected && onItemSelectedListener != null) {
            onItemSelectedListener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
        lastPosition = position;
    }
}

设置监听器 -

spn.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            Log.d("onItemSelected", String.valueOf(position));
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            
        }
    });

2
如果仍然有效,则回调的正确调用应该是:
@Override
public void setSelection(int position) {
    super.setSelection(position);
    if(listener != null)
        listener.onItemSelected(this, getChildAt(position), position, 0);
}

这不是正确的回调函数。首先,在最后一个参数中应使用 getItemIdAtPosition(position),其次在此情况下不应使用getChildAt(position)SpinnerListView 一样使用“视图回收”机制,因此不能使用getChildAt从适配器检索第position个项目。 - WonderCsabo
你对获取正确ID的最后一个参数是正确的。但是为了获取视图,使用getChildAt()是可以的,因为我们要么得到一个正确的视图(如果存在),要么得到null(如果不存在)。在大多数情况下,当项目被点击并且在Spinner中可见时,代码会被调用,因此getChildAt()返回正确的视图。至少我从未遇到过返回错误视图的情况。 - Martin Edlman
1
不,你是不正确的。例如,旋转器有100个项目,最后一个被点击了。但只有10个可见。在这种情况下,getChildAt()将始终返回null,因为它没有100个子Views。您不能依赖于任何AdapterViews的getChildAt()。 您可以调用onItemSelected(this, getSelectedView(), position, getItemIdAtPosition(position)); - WonderCsabo
也许我错了,但是你的意思是getChildAt()只返回索引0-9(10个可见项)的视图吗?我没有尝试过,但听起来很奇怪。我期望它应该为适配器中所有项目返回视图 - 更具体地说,如果它可见,则应返回视图,否则返回null。我会在有空闲时间时尝试一下,但现在我没有。当我点击第100个项目(pos 99)时,有100个项目(索引0-99)的Spinner会返回null?我不明白。当我点击最后一个项目时,它必须是可见的,getChildAt(99)必须返回它。或者不是这样吗? - Martin Edlman
你是否了解视图回收?并不是每个项目都有一个View,只有一些必要的View被添加。这就是为什么getChild(99)会返回null。10和99只是例子,显然它取决于当前情况。 - WonderCsabo

1
最简单的解决方案:

spinner.performItemClick(view,position,id)


0

我曾经遇到过同样的问题,我通过在每次适配器更改项目时设置onItemSelectedListener来解决它。


0

根据@Vishal Yadav的答案,如果你想通过调用spinner.setSelection(pos, false);来设置初始位置而不触发OnItemSelectedListener,则自定义Spinner应该是:

public class CSpinner extends AppCompatSpinner {

    private int lastPosition = 0;

    public CSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setSelection(int position, boolean animate) {
        OnItemSelectedListener listener = getOnItemSelectedListener();
        setOnItemSelectedListener(null);
        super.setSelection(position, animate);
        lastPosition = position;
        setOnItemSelectedListener(listener);
    }

    @Override
    public void setSelection(int position) {
        super.setSelection(position);
        boolean sameSelected = lastPosition == getSelectedItemPosition();
        OnItemSelectedListener onItemSelectedListener = getOnItemSelectedListener();
        if (sameSelected && onItemSelectedListener != null) {
            onItemSelectedListener.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
        }
        lastPosition = position;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接