微调器:当所选项目保持不变时,不调用 onItemSelected

Posted

技术标签:

【中文标题】微调器:当所选项目保持不变时,不调用 onItemSelected【英文标题】:Spinner : onItemSelected not called when selected item remains the same 【发布时间】:2012-06-06 22:24:46 【问题描述】:

我的Spinner 有一个OnItemSelectedListener,但是当所选项目与前一个相同时,它不会被调用。显然OnClickListener 不是Spinner 的选项。 每次用户单击某个项目时,我都需要捕捉。有什么想法吗?

也许Spinner 位于ActionBar 内部这一事实会干扰正常行为?

@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) 
        
    );

【问题讨论】:

你看这篇文章了吗***.com/questions/3928071/… 是的,这并没有帮助......无论如何,我刚刚找到了解决方案,并准备在这里写下来=) 这不相关,但将 Spinner 可见性设置为 VIEW.GONE 也会导致此问题。 【参考方案1】:

从@Vishal Yadav 的回答进一步,如果您想通过调用spinner.setSelection(pos, false); 来设置初始位置而不触发 OnItemSelectedListener,那么自定义微调器应该是:

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;
    

【讨论】:

【参考方案2】:

如果它仍然是实际的,正确的回调调用应该是

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

【讨论】:

这不是正确的回调。首先,您应该使用getItemIdAtPosition(position) 作为最后一个参数,第二个getChildAt(position) 在这种情况下不应该使用。 Spinners 使用View 回收就像ListViews 一样,所以getChildAt 不能用于从适配器中检索位置的第项。 最后一个参数是正确的,以获得正确的 ID。但是要获得视图,可以使用 getChildAt() ,因为如果它存在,我们将获得正确的视图,如果不存在,我们将获得 null。在大多数情况下,当单击项目并因此在微调器中可见时会调用代码,因此 getChildAt() 返回正确的视图。至少我从来没有遇到过返回错误视图的情况。 不,你不正确。例如,微调器有 100 个项目,最后一个被单击。但是只有10个可见。在这种情况下,getChildAt() 将始终返回 99 位置的 null,因为它没有 100 个子 Views。你不能依赖getChildAt() 和任何AdapterViews。您可以致电onItemSelected(this, getSelectedView(), position, getItemIdAtPosition(position)); 也许我弄错了,但您是否想说 getChildAt() 仅返回索引 0-9 的视图(对于 10 个可见项目)?我没试过,但听起来很奇怪。我希望它应该从底层适配器返回适配器中所有项目的视图 - 更具体地说,如果它是可见的,它应该返回视图,否则它应该返回 null。当我有一些我现在没有的空闲时间时,我会尝试。当我单击第 100 个项目(位置 99)时,具有 100 个项目(索引 0-99)的微调器将返回 null?我不明白。当我单击最后一项时,它必须是可见的并且 getChildAt(99) 必须返回它。还是不行? 你知道view recycling吗?每个项目都没有Views,只添加了一些必要的Views。这就是getChild(99) 将返回 null 的原因。 10 和 99 只是示例,显然这取决于当前情况。【参考方案3】:

这是一个更好的实现 -

自定义微调器类 -

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

【讨论】:

点赞!!!你拯救了我的一天。【参考方案4】:

我找到了一个简单的解决方案

只需再次调用 setAdapter 来代替第二个微调器的 notifyDataSetChanged

【讨论】:

【参考方案5】:

重写了通用解决方案,但使用:

    牢记androidx 扩展自AppCompatSpinner 使用内置的OnItemSelectedListener 侦听器而不是创建自己的侦听器 添加了初始侦听器调用技巧

这里:

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

【讨论】:

太棒了,谢谢。这解决了我在使用微调器时遇到的问题。 布尔sameSelected = position == getSelectedItemPosition();始终为 TRUE,getSelectedItemPosition() 方法返回与位置相同的值。【参考方案6】:

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

【讨论】:

【参考方案7】:

要让你的微调器改变,尽管最后一个索引的值是选择的,只需使用:

spinner.setSelection(0); 

在调用其他选择之前

spinner.setSelection(number); 

这样,微调器将触发两次 OnItemSelected 事件。只需确保第二次执行您需要的任何操作即可。

【讨论】:

同一个item位置为0怎么办? spinner.setSelection(0); if(number!=0) spinner.setSelection(number);【参考方案8】:

这里有一个更好的实现:

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;
    

【讨论】:

【参考方案9】:

最简单的解决方案:

spinner.performItemClick(view,position,id)

【讨论】:

【参考方案10】:

我发现了这项工作而不是提供的工作

/** 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());
    
  

【讨论】:

如果没有设置OnItemSelectedListener并且选择了相同的项目,你将得到一个NPE。 这个也为我工作,接受的答案没有 感谢原作者:***.com/a/11323043/3726133【参考方案11】:

好的,我终于找到了解决方案,通过创建我自己的扩展 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;


【讨论】:

你能分享一些关于如何为ActionBar导航列表实现相同功能的想法吗? @benoffi7 你的答案在哪里? @dhams 抱歉,我的回答有误,我将其删除。 @elgui 这一行 listener.onItemSelected(null, null, position, 0);给我一个 NPE,你能告诉我如何让 AdapterView & View 在上面的行中通过。谢谢 如何在 listener.onItemSelected(null, null, position, 0); 中获取父级和视图?

以上是关于微调器:当所选项目保持不变时,不调用 onItemSelected的主要内容,如果未能解决你的问题,请参考以下文章

所选项目上的 Android 微调器不起作用?

如何在单击按钮上从微调器中删除所选项目?

Kotlin Spinner OnItemSelected 意图

以编程方式设置微调器的选定项

使用线程获取图像资源时,意图选择器保持打开状态

当其他微调器更改时保持微调器选择