问答中心分类: ANDROID如何避免新实例化的微调器触发?
0
匿名用户 提问 1月 前

我想了一些不那么优雅的方法来解决这个问题,但我知道我肯定错过了什么。
我的onItemSelected在不与用户进行任何交互的情况下立即触发,这是不希望出现的行为。我希望用户界面在做任何事情之前都能等到用户选择某个东西。
我甚至试着在电视里设置听众onResume(),希望这会有所帮助,但事实并非如此。
在用户可以触摸控件之前,我如何阻止它触发?

public class CMSHome extends Activity { 

private Spinner spinner;

@Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Heres my spinner ///////////////////////////////////////////
    spinner = (Spinner) findViewById(R.id.spinner);
    ArrayAdapter adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    };

public void onResume() {
    super.onResume();
    spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}

    public class MyOnItemSelectedListener implements OnItemSelectedListener {

    public void onItemSelected(AdapterView> parent,
        View view, int pos, long id) {

     Intent i = new Intent(CMSHome.this, ListProjects.class);
     i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
        startActivity(i);

        Toast.makeText(parent.getContext(), "The pm is " +
          parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
    }

    public void onNothingSelected(AdapterView parent) {
      // Do nothing.
    }
}
}
Günay Gültekin 回复 1月 前

你可以看看这个解决方案,它简单实用。堆栈溢出。com/a/10102356/621951

Günay Gültekin 回复 1月 前

一个简单的解决方案是在Spinner空空如也onItemSelected您可以检测字符串是否为空startActivity!

Günay Gültekin 回复 1月 前

这种模式运行正常堆栈溢出。com/questions/13397933/…

30 Answers
0
Brad 回答 1月 前

Runnable的使用是完全错误的。
使用setSelection(position, false);在初选之前setOnItemSelectedListener(listener)
通过这种方式,您可以在不使用动画的情况下设置选择,这会导致调用“on item selected listener”。但是监听器为null,因此不运行任何内容。然后你的听众就被指派了。
所以按照这个顺序:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
pkk 回复 1月 前

+1个隐藏的宝石!将false作为“animate”参数传递不会调用侦听器回调。令人惊叹的

Martin T. 回复 1月 前

+1个奇怪但优雅的解决方案:)幸运的是,我已经不得不打电话给她了。。。

Rudi Kershaw 回复 1月 前

当Spinner UI元素被组装时,监听器仍然会启动,因此无论这不会阻止OP描述的不需要的行为,监听器都会启动。如果在onCreateView()期间或之前没有声明,这非常有效,但这不是他们要求的。

Gem 回复 1月 前

哦,这是没有任何黑客攻击的更好解决方案!!

Senchay 回复 1月 前

它正好解决了我的问题。谢谢@鲁迪你确定吗?我在onCreate中构建了我的布局,使用它我的toast不会像在这行代码之前那样启动。我在用户选择第一个微调器条目时触发toast,而在开始活动时始终触发toast之前。现在使用htis行,toast只在用户选择第一个条目时触发,而不是在启动活动时触发。

live-love 回复 1月 前

迈克尔以前也有同样的答案。

ToolmakerSteve 回复 1月 前

有用,但解决的问题与OP提出的不同。OP指的是(不幸的是)在视图首次出现时自动触发的选择事件尽管程序员没有做任何选择.

Vivek Warde 回复 1月 前

它的价值应该是什么position?

Brad 回复 1月 前

position应该是来自[你的听众]的价值观(开发商安卓com/reference/android/widget/…?>, 安卓看法视图,int,long)

Tushar Gogna 回复 1月 前

如何在使用黄油刀时做到这一点?我卡住了!

Shirish Herwade 回复 1月 前

真棒的回答。。。因此,upvote,但您应该更新它,以添加“位置”将已经是列表中选定项的位置的信息。我花了5分钟才明白自己应该处于什么位置。

Dani 回复 1月 前

setSelection(..)中的“false”值参数方法是我的解决方案。泰!

Sruit A.Suk 回复 1月 前

选举(职位);也不起作用,必须使用setSelection(position,false);让它发挥作用

superUser 回复 1月 前

“Runnable的使用是完全不正确的”。我同意这一点。这是一种更好的优化方法。谢谢

Ziv Kesten 回复 1月 前

这应该是公认的答案——这是最普遍的答案,如果从代码的不同部分以不同的预期结果初始化微调器,那么使用布尔值并不总是有效的

incognito 回复 1月 前

我找到的最简单的解决方法。但请记住,如果为职位分配了-1,那么出于任何奇怪的原因,它都不会起作用。使用适配器。没有默认值的_选择。

scai 回复 1月 前

对我来说,这可以防止旋转器在选择第一个元素时触发。其他元素也可以。

kashyap jimuliya 回复 1月 前

按照相同的顺序,仍然触发click listener!

CACuzcatlan 回复 1月 前

这不适用于适配器的数据绑定。即使我在设置选择后附加了click listener,它仍然会激发click listener。如果我在未附加onItemSelectedListener的情况下设置选择,则不会触发任何操作(如预期的那样)。

asifsid88 回复 1月 前

令人惊讶的是,这应该是公认的答案s.setSelection(position, false);将“动画值”(animate value)设置为false会起到作用。没有黑客!清洁溶液。

mallaudin 回复 1月 前

而且s.setSelection(position, false);应在设置适配器后调用。

szaske 回复 1月 前

每个人似乎都在讨论几件不同的事情。我之所以来到这里,是因为当你为旋转器设置适配器时,OnItemSelected会触发。这就是让我讨厌的地方。我将异步获取数据,并在获取数据后填充微调器,然后选择视图。没有选择一个项目。这是一只虫子。

MikeOscarEcho 回复 1月 前

@同样的问题,我感觉到了你的痛苦。坦率地说,我认为这个“漏洞”的存在是非常荒谬的。谁会想到在初始化时调用侦听器?有人知道在什么情况下这会对你有益吗?

Shishir Gupta 回复 1月 前

为什么网络搜索不能直接指向这个解决方案呢!非常感谢你找到了正确的方法,而不是让我觉得“安卓API不可能如此愚蠢”的黑客方法。

0
casaflowa 回答 1月 前

参考Dan Dyer的答案,试着注册OnSelectListener在一个post(Runnable)方法:

spinner.post(new Runnable() {
    public void run() {
        spinner.setOnItemSelectedListener(listener);
    }
});

通过为我这样做,我的愿望终于实现了。
在这种情况下,它还意味着侦听器仅对更改的项激发。

Jakob 回复 1月 前

我收到一个错误消息:AdapterView类型中的方法setOnItemSelectedListener(AdapterView.OnItemSelectedListener)不适用于参数(new Runnable(){})为什么?

kenny_k 回复 1月 前

这实际上不是在可运行线程和UI线程之间设置了竞争条件吗?

Richard Le Mesurier 回复 1月 前

@未知工程师-此代码应该从主线程方法之一运行,例如。onCreate(),onResume()在这种情况下,这是一个神奇的把戏,没有比赛条件的危险。我通常会用这个技巧onCreate()就在布局代码之后。

pmont 回复 1月 前

@普拉哈——在CM11上效果很好

jophde 回复 1月 前

这是一个很好的解决方案,绝对不是黑客!这种功能是在框架的深处完成事情的方式。可惜Spinner没有在内部这么做。然而,这是保证某些代码在活动创建后运行的最干净的方法。这是因为当活动试图通知侦听器时,侦听器尚未设置在微调器上。

Patrick Dorn 回复 1月 前

小提示:如果您在ListView(RecyclerView)中使用微调器,不要忘记调用微调器。setOnItemSelectedListener(null),然后在viewHolder中重置适配器,以避免调用“旧”侦听器。

Kuldeep Dhaka 回复 1月 前

这是一个可接受的解决方案.不是瞎开枪。其他解决方案在未来更容易出现行为改变问题。

0
CommonsWare 回答 1月 前

我本以为您的解决方案会起作用——我想如果您在设置侦听器之前设置了适配器,选择事件不会触发。
这就是说,一个简单的布尔标志将允许您检测流氓第一选择事件并忽略它。

FauxReal 回复 1月 前

啊,是的。这就是我所说的不雅观的解决方案。看来一定有更好的办法。谢谢你。

BoD 回复 1月 前

Dev ml上的这个线程对此有更多的了解:组。谷歌。com/group/android developers/browse_-thread/thread/…-不幸的是,没有给出解决方案。。。

Dan Dyer 回复 1月 前

布局组件的过程会触发选择侦听器。因此,您必须添加侦听器之后布局已经完成。我一直无法找到一个合适的,直接的地方来做这件事,因为布局似乎发生在某个点之后onResume()onPostResume(),所以在布局发生时,所有正常的钩子都已完成。

daniel.gindi 回复 1月 前

我会远离这个布尔标志——就好像未来行为的改变可能会导致一个bug。更可靠的解决方案是保留一个带有“当前选定索引”的变量,并将其初始化为选定的第一项。然后在选择事件中——检查它是否等于新位置——返回,什么也不做。当然,在选择时更新变量。

Siddharth 回复 1月 前

这行不通。答案由@casanova works提供。这应该是公认的答案。

VipulKumar 回复 1月 前

这是正确的,但你可以再做一件事。设置适配器。包括mSpinner.setSelection(0, false);设置听众。我希望它能起作用。

Alex 回复 1月 前

每个旋转器必须有一面旗帜,因为它没有用。此外,如果你的微调器出现碎片,你离开应用程序,ramBooster会清理,然后你回到应用程序,微调器会启动两次

Violet Giraffe 回复 1月 前

这并不能回答这个广受欢迎的问题。它应该是一个评论,而不是一个答案,尤其不是公认的答案。karooolek提供了真正可靠的答案:堆栈溢出。com/a/13528576/634821

CommonsWare 回复 1月 前

@VioletGiraffe:“这并不能回答这个非常流行的问题”——当然可以:“一个简单的布尔标志可以让你检测到流氓第一选择事件并忽略它”。你可能不喜欢这个答案。同样,我也不喜欢你强调的答案,因为它对事件的顺序和时间做出了毫无根据的假设,这些假设可能不适用于Android版本和制造商对Android的调整。然而,我绝不会声称你选择的答案不是答案。这是一个答案,只是我不同意。

Violet Giraffe 回复 1月 前

@Commonware:我明白,你说得有道理。我犯了一个错误,认为这个问题比实际情况更广泛:我需要防止OnItemSelected每次启动时回拨,以响应setSelection,这个问题只讨论了第一次,在最初的UI布局过程中。为此,您的解决方案更好。我指出的那个更具普遍性,但在未来可能不可靠,尽管我预计它会持续很长时间。然而,如果你问我,暗示一个答案仍然不是真正的答案。

LarsH 回复 1月 前

是否保证永远只有一个“流氓优先选择事件”?如果是,我们怎么知道?从文件上看似乎不清楚。如果没有这一保证,这个解决方案似乎也会做出毫无根据的假设,这些假设可能无法支撑Android版本和制造商对Android的调整。

CommonsWare 回复 1月 前

@拉尔斯:根据这个论点,你不能保证得到任何选择事件。或者你可以在每次选择中得到2000次回调。我完全同意我的解决方案过于简单。它也有九年多的历史了,那时我们可以用一只手数一数Android设备型号的数量,只剩下手指。依我拙见Spinner对于这个特定的问题,它不是一个设计得特别好的小部件。如果你需要控制实施,调查第三方替代品.

LarsH 回复 1月 前

“根据这种说法,你无法保证获得任何选择事件。”为什么呢文件上说setOnItemSelectedListener()将“注册一个回调,在选择此AdapterView中的项目时调用。”开发商安卓com/guide/topics/ui/controls/spinner?hl=en还表示“当用户从下拉列表中选择一个项目时,微调器对象将收到一个on item selected事件。”

CommonsWare 回复 1月 前

@拉尔斯:你部分担心的是制造商的调整。制造商没有法律义务遵守这些文件。只要他们通过CTS并遵守CDD,他们就可以改变他们想要的。这可能包括打破人们的期望Spinner.我不会期望他们打破它,就像我不会期望他们打破我的答案所作的假设一样。但是,事情发生了。

LarsH 回复 1月 前

当然,MFR可以调整事情。但是,假设操作系统实现者将遵守记录的行为比假设他们将继续遵守未记录的行为要安全得多。换句话说,这感觉就像你在画一个错误的等价物。

LarsH 回复 1月 前

我对你的答案本身没有异议;有时,最好的方法是依靠似乎一直发生的未记录的行为。我只是不明白为什么你说你不喜欢Violet所指的解决方案(相对于你自己的),因为它会做出毫无根据的假设,而你的也会。我这么说是因为你的Android知识和能力赢得了我的尊重。

CommonsWare 回复 1月 前

@拉尔斯:我对维奥莱特评论的问题是,他声称这不是答案。如果在Violet的答案和我的答案之间做出选择,我会认为我的答案相对于他们各自的假设更安全,尽管我同意他们都做出了假设。我没有评估这个问题的其他答案,以了解其中哪些答案也做出了假设。如果你想避免这些假设,我的假设是Spinner这不是一个好选择。:-)

0
karooolek 回答 1月 前

我创建了一个用于更改Spinner在不通知用户的情况下选择:

private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
    final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
    spinner.setOnItemSelectedListener(null);
    spinner.post(new Runnable() {
        @Override
        public void run() {
            spinner.setSelection(selection);
            spinner.post(new Runnable() {
                @Override
                public void run() {
                    spinner.setOnItemSelectedListener(l);
                }
            });
        }
    });
}

它禁用侦听器,更改选择,然后重新启用侦听器。
诀窍在于,调用与UI线程是异步的,因此必须在连续的处理程序帖子中完成。

JStephen 回复 1月 前

令人惊叹的我有多个微调器,在设置它们的值之前,我尝试将它们的所有侦听器都设置为null,然后我将所有侦听器都设置回它们应该是的状态,但由于某种原因,这不起作用。尝试了这个功能,结果成功了。我不知道为什么我的不起作用,但这是有效的,所以我不在乎:D

Violet Giraffe 回复 1月 前

注意:如果你打电话setSpinnerSelectionWithoutCallingListener快速两次,以便在第一次呼叫已将侦听器设置为null,您的微调器将被卡住null永远的倾听者。我提出以下解决方案:添加if (listener == null) return;之后spinner.setSelection(selection).

0
Jorrit 回答 1月 前

不幸的是,对于这个问题,两种最常见的建议解决方案,即计算回调发生次数和发布Runnable以在以后设置回调,在启用可访问性选项等情况下,似乎都会失败。下面是一个帮助器类,可以解决这些问题。注释栏中有进一步的解释。

import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;

/**
 * Spinner Helper class that works around some common issues 
 * with the stock Android Spinner
 * 
 * A Spinner will normally call it's OnItemSelectedListener
 * when you use setSelection(...) in your initialization code. 
 * This is usually unwanted behavior, and a common work-around 
 * is to use spinner.post(...) with a Runnable to assign the 
 * OnItemSelectedListener after layout.
 * 
 * If you do not call setSelection(...) manually, the callback
 * may be called with the first item in the adapter you have 
 * set. The common work-around for that is to count callbacks.
 * 
 * While these workarounds usually *seem* to work, the callback
 * may still be called repeatedly for other reasons while the 
 * selection hasn't actually changed. This will happen for 
 * example, if the user has accessibility options enabled - 
 * which is more common than you might think as several apps 
 * use this for different purposes, like detecting which 
 * notifications are active.
 * 
 * Ideally, your OnItemSelectedListener callback should be
 * coded defensively so that no problem would occur even
 * if the callback was called repeatedly with the same values
 * without any user interaction, so no workarounds are needed.
 * 
 * This class does that for you. It keeps track of the values
 * you have set with the setSelection(...) methods, and 
 * proxies the OnItemSelectedListener callback so your callback
 * only gets called if the selected item's position differs 
 * from the one you have set by code, or the first item if you
 * did not set it.
 * 
 * This also means that if the user actually clicks the item
 * that was previously selected by code (or the first item
 * if you didn't set a selection by code), the callback will 
 * not fire.
 * 
 * To implement, replace current occurrences of:
 * 
 *     Spinner spinner = 
 *         (Spinner)findViewById(R.id.xxx);
 *     
 * with:
 * 
 *     SpinnerHelper spinner = 
 *         new SpinnerHelper(findViewById(R.id.xxx))
 *         
 * SpinnerHelper proxies the (my) most used calls to Spinner
 * but not all of them. Should a method not be available, use: 
 * 
 *      spinner.getSpinner().someMethod(...)
 *
 * Or just add the proxy method yourself :)
 * 
 * (Quickly) Tested on devices from 2.3.6 through 4.2.2
 * 
 * @author Jorrit "Chainfire" Jongma
 * @license WTFPL (do whatever you want with this, nobody cares)
 */
public class SpinnerHelper implements OnItemSelectedListener {
    private final Spinner spinner;

    private int lastPosition = -1;
    private OnItemSelectedListener proxiedItemSelectedListener = null;  

    public SpinnerHelper(Object spinner) {
         this.spinner = (spinner != null) ? (Spinner)spinner : null;        
    }

    public Spinner getSpinner() {
        return spinner;
    }

    public void setSelection(int position) { 
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position);     
    }

    public void setSelection(int position, boolean animate) {
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position, animate);        
    }

    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        proxiedItemSelectedListener = listener;
        spinner.setOnItemSelectedListener(listener == null ? null : this);
    }   

    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position != lastPosition) {
            lastPosition = position;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onItemSelected(
                        parent, view, position, id
                );
            }
        }
    }

    public void onNothingSelected(AdapterView<?> parent) {
        if (-1 != lastPosition) {
            lastPosition = -1;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onNothingSelected(
                        parent
                );
            }
        }
    }

    public void setAdapter(SpinnerAdapter adapter) {
        if (adapter.getCount() > 0) {
            lastPosition = 0;
        }
        spinner.setAdapter(adapter);
    }

    public SpinnerAdapter getAdapter() { return spinner.getAdapter(); } 
    public int getCount() { return spinner.getCount(); }    
    public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }   
    public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
    public Object getSelectedItem() { return spinner.getSelectedItem(); }
    public long getSelectedItemId() { return spinner.getSelectedItemId(); }
    public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
    public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
    public boolean isEnabled() { return spinner.isEnabled(); }
}
user3829751 回复 1月 前

这应该是投票率最高的答案。这很简单,但很精彩。它允许您保持当前的所有实现不变,除了初始化的那一行。这无疑让老项目的复古装修变得相当容易。除此之外,我还实现了OnTouchLisener界面,在spinner打开时关闭键盘,一举两得。现在我所有的纺纱机都按照我想要的方式运转。

jwehrle 回复 1月 前

回答得很好。当我将addAll()添加到适配器时,它仍然会触发到第0个元素,但我的第0个元素是中性(不做任何事情)行为的省略号。