我想过一些不那么优雅的方法来解决这个问题,但我知道我一定遗漏了什么。
我的onItemSelected立即启动,没有与用户进行任何交互,这是不希望的行为。我希望UI能够等到用户选择某样东西后再执行任何操作。
我甚至尝试在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<CharSequence> 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.
}
}
}
在遇到同样的问题后,我想出了这个使用标签的解决方案。
它背后的思想很简单:无论何时以编程方式更改旋转器,都要确保标记反映所选的位置。然后在侦听器中检查所选位置是否等于标记。如果是,则以编程方式更改了旋转器选择。
下面是我的新“spinner proxy”类:
package com.samplepackage;
import com.samplepackage.R;
import android.widget.Spinner;
public class SpinnerFixed {
private Spinner mSpinner;
public SpinnerFixed(View spinner) {
mSpinner = (Spinner)spinner;
mSpinner.setTag(R.id.spinner_pos, -2);
}
public boolean isUiTriggered() {
int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
int pos = mSpinner.getSelectedItemPosition();
mSpinner.setTag(R.id.spinner_pos, pos);
return (tag != -2 && tag != pos);
}
public void setSelection(int position) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position);
}
public void setSelection(int position, boolean animate) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position, animate);
}
// If you need to proxy more methods, use "Generate Delegate Methods"
// from the context menu in Eclipse.
}
在Values目录中还需要一个带有标记设置的XML文件。
我将我的文件命名为spinner_tag.xml,但这取决于您。
它是这样的:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="spinner_pos" type="id" />
</resources>
现在取代
Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);
在你的代码中
SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));
让你的处理器看起来像这样:
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (myspinner.isUiTriggered()) {
// Code you want to execute only on UI selects of the spinner
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
函数isUiTriggered()当且仅当旋转器已被用户更改时将返回true。注意,这个函数有一个副作用——它会设置标记,所以同一个监听器调用中的第二个调用总是返回false。
这个包装器还将处理在布局创建期间调用侦听器的问题。
玩得开心,
延斯。
这也不是一个优雅的解决方案。事实上,这有点像鲁布-戈德堡,但似乎很有效。通过扩展数组适配器并覆盖其getDropDownView,我确保旋转器至少被使用过一次。在新的getDropDownView方法中,我有一个布尔标志,被设置为显示下拉菜单至少被使用过一次。在设置标志之前,我会忽略对侦听器的调用。
MainActivity.onCreate ():
ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);
ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...
ArAd abAdapt = new ArAd (this
, android.R.layout.simple_list_item_1
, android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);
覆盖的数组适配器:
private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
private ArAd(Activity a
, int layoutId, int resId, ArrayList<String> list) {
super(a, layoutId, resId, list);
viewed = false;
}
@Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
viewed = true;
return super.getDropDownView(position, convertView, parent);
}
}
修改监听器:
@Override
public boolean onNavigationItemSelected(
int itemPosition, long itemId) {
if (viewed) {
...
}
return false;
}
不幸的是,解决这个问题的两个最常见的建议解决方案,即统计回调发生次数和发布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(); }
}
在抽出我的头发很长一段时间后,现在我已经创建了自己的Spinner类。我已经添加了一个方法,它可以适当地断开和连接侦听器。
public class SaneSpinner extends Spinner {
public SaneSpinner(Context context) {
super(context);
}
public SaneSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
OnItemSelectedListener l = getOnItemSelectedListener();
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(null);
}
super.setSelection(position, animate);
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(l);
}
}
}
在XML中像这样使用它:
<my.package.name.SaneSpinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mySaneSpinner"
android:entries="@array/supportedCurrenciesFullName"
android:layout_weight="2" />
你所要做的就是在膨胀和调用集选择之后检索SaneSpinner实例:
mMySaneSpinner.setSelection(1, true, true);
这样,就不会触发任何事件,用户交互也不会中断。这大大降低了我的代码复杂性。这应该包括在Android的股票,因为它确实是一个PITA。