How to disable onItemSelectedListener to be invoked when setting selected item by code
Just wondering how you handle the following problem: a开发者_运维问答 result is calculated depending on two spinners' selected items. To handle the UI things, i.e. a user picks a new item in one of the spinners, I install a listener using setOnItemSelectedListener
for the spinner in my onCreate()
method of the activity.
Now: that works, of course, fine. The listener's work is to trigger a new calculation of the result.
The problem: because I intercept onPause()
onResume()
to save/restore the last state, I got a method that sets these two spinners' selected item programmatically like in here:
startSpinner.setSelection(pStart);
destSpinner.setSelection(pDest);
These two calls invoke the listeners, too! My calculation method for the result plus the notification of a new result set is invoked twice here!
A stupid direct approach for this would be to have a boolean variable disabling whatever the listener does inside, setting it before setting the selected items and resetting it afterwards. Okay. But is there a better method??
I don't want listeners to be called by code - actions, only by user actions! :-(
How do you do it? Thanks!
A cleaner solution, in my opinion, to differentiate between programmatic and user-initiated changes is the following:
Create your listener for the spinner as both an OnTouchListener and OnItemSelectedListener
public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
boolean userSelect = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
userSelect = true;
return false;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (userSelect) {
// Your selection handling code here
userSelect = false;
}
}
}
Add the listener to the spinner registering for both event types
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
This way, any unexpected calls to your handler method due to initialization or re-initialization will be ignored.
Okay, I got it working the way I want to now.
The thing to understand here (and I did not when I was writing that question...) is that everything in Android runs in one thread - the UI thread.
Meaning: even though you set Spinner's values here and there: they are only updated (visually) and their listeners are only called after all methods you're currently in (like onCreate
, onResume
or whatever) are finished.
This allows the following:
- keep the selected positions in field variables. (like
currentPos1
,currentPos2
) - the listeners
onItemSelectedListener()
call a method likerefreshMyResult()
or whatever. - when setting positions programmatically, set the spinners and call your own refresh method manually right after that.
The refreshMyResult()
method looks like this:
int newPos1 = mySpinner1.getSelectedItemPosition();
int newPos2 = mySpinner2.getSelectedItemPosition();
// only do something if update is not done yet
if (newPos1 != currentPos1 || newPos2 != currentPos2) {
currentPos1 = newPos1;
currentPos2 = newPos2;
// do whatever has to be done to update things!
}
Because the listeners will be called later - and by then, the remembered position in currentPos is already updated - nothing will happen and no unnecessary update of anything else will take place. When a user selects a new value in one of the spinners, well - the update will be performed accordingly!
That's it! :-)
Ahh - one more thing: the answer to my question is: No. The listeners cannot be disabled (easily) and will be called whenever a value is changed.
I have an easier, and I think, better solution. Since I had to refresh the spinners even after initialization, this is a more generic approach. Please refer the accepted answer:
Undesired onItemSelected calls
First add boolean values for stopping spinner listener call
Boolean check = false;
Then you add on Touch listener and on Item click Listener Like below code
holder.filters.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
check = true;
return false;
}
});
holder.filters.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id)
{
flag = filterids.get(position);
if(check)
{
check = false;
new Applyfilters().execute(flag,"3");
}else{
}
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{
// TODO Auto-generated method stub
}
});
Its simple working good for stopping server call multiple times.
It is very easy you can call the Spinner.setSelection(int position, boolean animate)
method with false
so the listeners will not react on the change.
Spinner.setSelection(int position, boolean animate) does trigger the listener on 4.3
I created a library that help for all, that no need to call item onClick action in Spinner For example:
spinner.setSelection(withAction,position);
where withAction is a boolean flag, that used for call or not item action
Link on Github: https://github.com/scijoker/spinner2
My solution is very easy. First initialize a global boolean variable.
boolean shouldWork = true;
Then use below code in your onCreate() method.
Spinner spinner = findViewById(R.id.spinner);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView adapter, View v, int i, long lng) {
if (shouldWork) {
// Do your actions here
}
else
shouldWork = true;
}
public void onNothingSelected(AdapterView<?> parentView) {
}
});
Now you can use the setSelection method in everwhere without invoking the onItemSelected() method by below code.
shouldWork = false;
spinner.setSelection(0);
Add the OnItemSelectedListener
for each spinner after you have set any previous value in onResume
.
When Spinner.setSelection(position) is used, it always activates setOnItemSelectedListener()
To avoid firing the code twice I use this solution:
private mIsSpinnerFirstCall=true;
...
Spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//If a new value is selected (avoid activating on setSelection())
if(!mIsSpinnerFirstCall) {
// Your code goes gere
}
mIsSpinnerFirstCall = false;
}
public void onNothingSelected(AdapterView<?> arg0) {
}
});
This solution is valid when you are sure that Spinner.setSelection(position) us used. Also, it is important to set mIsSpinnerFirstCall=true each time before using Spinner.setSelection(position)
In my case, since I'm triggering spinner programmatically, then I just have to add spinnerSelected flag after spinner.performClick() like below.
private var spinnerSelected = false
someView.setOnClickListener {
spinner.performClick()
spinnerSelected = true
}
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
// do nothing
}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (spinnerSelected) {
//... do something
spinnerSelected = false
}
}
}
This following method will help you to stop invoking automatically the selection listener
yourspinnerobj.post(new Runnable() {
@Override
public void run() {
yourspinnerobj.setOnItemSelectedListener(yourspinnerlistener);
}
});
精彩评论