I have a layout with three spinners. They differ in the option presented in the drop-down.
In my onCreateView I have a method to setup the spinners. Inside that method I have something like this:
mySpinner = (Spinner) view.findViewById(R.id.my_spinner);
ArrayAdapter<String> mySpinner =
new ArrayAdapter<String>(getActivity(), R.layout.background,
new ArrayList<String>(Arrays.asList(getResources().getStringArray(R.array.spinner_one_data))));
mySpinner.setDropDownViewResource(R.layout.spinner_text);
mySpinner.setAdapter(mySpinner);
mySpinner.setOnItemSelectedListener(this);
As I said, my other two spinners are almost the same but with different options.
I know that onItemSelected is called once for every spinner in a "first setup" so I have a flag to prevent this problem. With this flag solution, my spinners are working as expected.
The problem is when I select in each spinner an option and then rotate the screen. Now, onItemSelected is called 6 times instead the 3 times that I was expecting (I've set a flag to manage this situation of the 3 times calling).
Why Is it happening and hoe should I handle this?
In general, I've found that there are many different events that can trigger the onItemSelected method, and it is difficult to keep track of all of them. Instead, I found it simpler to use an OnTouchListener to only respond to user-initiated changes.
Create your listener for the spinner:
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 as both an OnItemSelectedListener and an OnTouchListener:
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
I've found a solution that is working for me.
I have the 3 spinners so onItemSelected is called 3 times at the initial spinner setup. To avoid onItemSelected from firing a method in the initial setup I've created a counter so onItemSelected only fires the method accordingly the counter value.
I've realized that in my situation, if a rotated the screen, onItemSelected is fired again the 3 times, plus a time for each spinner that is not in the position 0.
An example:
I have the 3 spinners and the user changes 2 of them to one of the available option other then position 0 so he ends up with a situation like this:
First spinner - > Item 2 selected
Second spinner -> Item 0 selected (no changes)
Third spinner -> Item 1 selected
Now, wen I rotate the screen, onItemSelected will be fired 3 times for the initial spinner setup plus 2 times for the spinners that aren't at position 0.
#Override
public void onSaveInstanceState(Bundle outState) {
int changedSpinners = 0;
if (spinner1.getSelectedItemPosition() != 0) {
changedSpinners += 1;
}
if (spinner2.getSelectedItemPosition() != 0) {
changedSpinners += 1;
}
if (spinner3.getSelectedItemPosition() != 0) {
changedSpinners += 1;
}
outState.putInt("changedSpinners", changedSpinners);
}
I've saved the state in onSaveInstanceState and then, in onCreateView I checked if savedInstanceState != null and if so, extracted changedSpinners from the bundle and updated my counter to act accordingly.
To expand on Andres Q.'s answer... If you are using Java 8 you can do this with fewer lines of code by making use of lambda expressions. This method also forgoes the need to create a separate class in order to implement onTouchListener
Boolean spinnerTouched; //declare this as an instance or class variable
spinnerTouched = false;
yourSpinner.setOnTouchListener((v,me) -> {spinnerTouched = true; v.performClick(); return false;});
yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if(spinnerTouched){
//do your stuff here
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
//nothing selected
}
});
What about just to check if fragment is in resumed state? Somethink like this:
private AdapterView.OnItemSelectedListener mFilterListener = new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (isResumed()) {
//your code
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
};
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
//set mFilterListener
}
It eliminates the rotation problem and also the first setup problem. No flags etc. I was having the same problem with TextWatchers and found this answer with comment, which inspired me for this solution.
Related
Is there anyway to prevent double tap on ListView in Android? I found this when i accidentally tapped item on ListView and it opened up two new window. is there any way to prevent it from opening a same window twice.
Just add listView.setEnabled(false); on select of listview and after select when response will come or back button press just write---- listView.setEnabled(true);
You should restrict the target activity (one that opens when an item is clicked) to have only one instance at any point of time.
Answer to this SO question should help you in achieving that. That way if you accidentally double click, you will still see just one new screen.
Have a try with introducing and Override of isEnabled method
#Override
public boolean isEnabled(int position) {
return false;
}
for the listview.
introduce a boolean for flag and an int to maintain last clicked position
int recentlyClickedPoisition;
boolean flagRecentClickedPoisition;
override the isEnabled method as follows
#Override
public boolean isEnabled(int position) {
if (flagRecentClickedPoisition && recentlyClickedPoisition == position) {
return false;
} else {
return true;
}
}
than set the last clicked position from your click listener as follows
public void setLastClickedPoisition(int recentlyClickedPoisition) {
flagRecentClickedPoisition = true;
this.recentlyClickedPoisition = recentlyClickedPoisition;
}
Hope this will work for you to prevent from double tap, enhance accordinly.
If you are using just single item like TextView in list then just create a class implements OnItemClickListener in this call and then in to onItemClick() method initialize myListView.setOnItemClickListenet(null);
then use Handler.postDelayed method to again set onItemClickListener.
like
myListView.setOnItemClickListener(new MyClickListenerClass());
This is working for all time in my case.
In your XAML view page place isEnable property as a bindable and two way mode.
<ListView ItemsSource="{Binding items}"
x:Name="listview"
HasUnevenRows="True"
IsEnabled="{Binding IsEnable, Mode=TwoWay}"
RowHeight="10"
SelectionMode="Single"
VerticalScrollBarVisibility="Never">
In viewmodel of your xaml page :
private bool _isEnable = true;
public bool IsEnable
{
get => _isEnable;
set
{
_isEnable = value; OnPropertyChanged(nameof(IsEnable));
}
}
public ICommand TapCommand => new Command<//Model>(async (obj) =>
{
IsEnable = false;
//your stuff
IsEnable = true;
});
I have a two pane layout, a listview that controls a detail view. First I thought a delayed handler is the worst idea, but after testing it is the simplest. Otherwise I would have to communicate between activity and fragment to enable the listview item once another detail was loaded. Error prone and complex.
/*example with Handler():*/
final boolean[] allowClick = {true};
myview.setOnClickListener(v -> {
//exit if not allowed
if(!allowClick[0])
return;
//do stuff
//we clicked, block another click
allowClick[0] =false;
//wait 0.7 seconds and allow another click
new Handler().postDelayed(() -> allowClick[0] =true, 700);
});
This solution is implemented on a ListFragment. If the tap dismissed the ListFragment to show a detail view (which it normally would), the next time the ListFragment appears, the tap counter is reset in OnResume():
public class MyListFragment extends ListFragment {
int mTapCounter;
#Override
public void onResume() {
super.onResume();
//Set-Reset ListView clickListener
mTapCounter = 0;
}
//ListView item tap event handler
#Override
public void onListItemClick(#NonNull ListView l, #NonNull View v, int position, long id) {
//Disable click listener to prevent double-tap
mTapCounter++;
//Only process single-tap
if(mTapCounter == 1) {
/* YOUR TAP HANDLER CODE HERE */
}
super.onListItemClick(l, v, position, id);
}
}
I need to change the Adapter of my Spinner exactly at the moment when I click on it.
I'm trying to display a Spinner with The value "Make Your choice" and then when the user click, another adapter is loaded on the Spinner and he can make his choice (without displaying the "Make Your choice").
Here is my code
ArayAdapter adapterclasse = new ArrayAdapter(this,android.R.layout.simple_spinner_item, affp.classes);
ArrayAdapter adaptermodule = new ArrayAdapter(this,android.R.layout.simple_spinner_item, affp.matiers);
spinner.setAdapter(adapterclasse);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
spinner.setAdapter(adaptermodule);
}
#Override
public void onNothingSelected(AdapterView<?> parentView) {
// your code here
}
});
but it's not working. When the app is launched, I got the second adapter loaded instead of the first.
Please help and thanks.
The second adapter is set because when the spinner is loaded the method onItemSelected is called, that's normal (the first element is selected).
Anyway, in general I don't think you need 2 adapters, you can just put "Make Your choise" on top and onItemSelected if the user has selected that, you can stop him and ask to select a real value.
Actually, it's possible to load a new adapter when the user touch the view (spinner)
spinner.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
spinner.setAdapter(adapterclasse2);
return false;
}
});
i have created two spinners both of different class.i want to set content of one spinner based on selected value of other spinner which is having in other class.can any one help me.thanks in advance.
There is a solution,you might feel it lengthy to implement but it would perfectly work for you,i think!
Lets say you have spinner_one in your ActivityOne.class and spinner_two in your ActivityTwo.class. And you want to populate spinner_2 based on what is selected in spinner_1.
Then,
spinner_1.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// save your selected item into a SharedPreference Variable
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
Now,for spinner_2:
String spinner1_value=get value from SharedPreference;
if(spinner1_value.equals("something_1"))
{
//populate spinner_2 accordingly
}
else if(spinner1_value.equals("something_2"))
{
//populate spinner_2 accordingly
}
else
{
//populate spinner_2 accordingly
}
And don't forget to put this code to populate spinner_2 in onResume() of your ActivityTwo.class,so that it will reflect the changes user made in spinner_1 value.
This will allow you to change spinner_2 content according to spinner_1 value as and when user changes it.
The short answer is that you can do this programatically - either by assigning a new ArrayAdapter to the Spinner, or by removing/adding items from the existing ArrayAdapter.
The longer answer is that you have to be very careful in relying on OnItemSelectedListener.onItemSelected(), since it will not get called if the old selection and a new selection happen to be in the same position, and Spinner.getSelectedItemPosition(), since it can actually return positions greater than the number of items in the ArrayAdapter if you're removing items on the fly.
I have a system of three cascaded Spinners, which in turn can drive the contents of other Buttons and Texts. I was able to get 95% solutions until I recognized the above and changed my view of what was authoritative and what was not - by making the Spinner subservient to the logic that determines content, rather than the other way around. So rather than calling setSelection() and relying on the onItemSelected() callback, do all your cascading logic outside the handler, and only then call setSelection() and return back up the chain.
Adapted from my working code:
class Spinster extends Activity {
...
private void setSpinnerOne( int pos ) {
// 1. Do our own chores, etc
doSomeStuff();
mSomeText.setText( "Blah!" );
mSomeButton.setEnabled( some_condition );
// 2. Populate dependent Spinner based on pos
populateSpinnerTwoAdapter( pos );
// 3. Cascade by calling SpinnerTwo logic (not the Spinner itself)
lSpinnerTwoPos = someNiceFunction();
setSpinnerTwo( lSpinnerTwoPos );
// 4. Now set SpinnerOne
mSpinnerTwo.setSelection( pos );
}
private void setSpinnerTwo( int pos ) {
// Follows the same pattern as setSpinnerOne(), but cascades to SpinnerThree
}
private void setSpinnerThree( int pos ) {
// Follows the same pattern as setSpinnerOne(), but need not cascade
}
...
private OnItemSelectedListener item_select = new OnItemSelectedListener() {
public void onItemSelected( AdapterView parent, View v, int position, long id )
{
...
int lId = parent.getId();
if ( lId == R.id.spinner_one ) {
setSpinnerOne( position );
} else if ( lId == R.id.spinner_two ) {
setSpinnerTwo( position );
} else if ( lId == R.id.spinner_three ) {
setSpinnerThree( position );
}
}
}
}
I've left out a small detail here, that of setting and clearing a guard variable in the setSpinner*() methods so that they don't bother doing all their work again when onSelectedItemListener() calls them back. I think the basic logic is easier to show by leaving it out. (And they don't strictly need to be there - onItemSelected() will not be called the second time round.)
I could post a real, working example if this is deemed to abstract.
It appears that android's Spinner class (and possibly ListView in general, although I don't know for sure) calls your OnItemSelectedListener's onItemSelected() method after you call setAdapter(), even if the user hasn't explicitly selected anything yet.
I can see how this would be useful in many situations, but there are times when I only want onItemSelected() to be called when an item is actually specifically selected.
Is there a way to control this behaviour and have Spinner NOT call onItemSelected() after setting the adapter?
I haven't used this solution for very long yet so I'm not totally confident that it works as expected, but I've had luck so far with this workaround:
spinner.setOnItemSelectedListener( new OnItemSelectedListener() {
protected Adapter initializedAdapter = null;
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Always ignore the initial selection performed after setAdapter
if( initializedAdapter !=parent.getAdapter() ) {
initializedAdapter = parent.getAdapter();
return;
}
...
}
}
Is there a better way?
Add listener to spinner like below:
spinner.post(new Runnable(){
public void run()
{
spinner.setOnItemSelectedListener( new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
...
}
}
}
});
I've used the setTag and getTag methods, and created a resource id called "spinnerstate".
Then whenever I programmatically set the adapter, I set the "spinnerstate" tag to "init", and in the fired event, set it to "ready" and ignore the event. (note my code is Mono for Android se it will look different):
Set Adapter:
profileSpn.SetTag (Resource.Id.spinnerstate, "init");
profileSpn.Adapter = new ArrayAdapter (this, Android.Resource.Layout.SimpleSpinnerItem, items.ToArray ());
Item Selected event:
string state = (string)((Spinner)sender).GetTag (Resource.Id.spinnerstate);
if (state == "init") {
((Spinner)sender).SetTag (Resource.Id.spinnerstate, "ready");
return;
}
I agree that this is not desired behaviour in almost 100% of cases, and I don't think it's good design on the part of Google, but there you go.
I did similar things before, I used count value. Using parent adapter object is incomplete because it can be a problem when view is refreshed or getView() called again.
Therefore, I recommend that using array of counter.
At first, define array of count in adapter globally.
private int isInitializedView[];
And then initialize it on getView().
isInitializedView[position] = 0;
In the selection listener, do something that you want if it already initialized.
holder.mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
isInitializedView[position]++;
if(isInitializedView[position] > 1) {
// do someting that you want
}
}
#Override
public void onNothingSelected(AdapterView<?> parentView) {}
});
(Note that isInitializedView[position]++; can be come after if() routine, and only trigger event when this value is >0 . It's your choice.)
I had three spinner in my activity and all spinner adapter data has been filled at runtime(from web-service data after call from onCreate method). So it automatically call onItemSelected(AdapterView<?> parent, View view, int position, long id) method of spinner.
I solved this issue by using onUserInteraction() method of activity
check this method that user is interacting with spinner or not. if yes then perform the action else not
Declare isUserIntract boolean variable globally
in onItemSelected method use following procedure
If(isUserIntract)
{
//perform Action
}
else{
//not perform action
}
use below code in activity
#Override
public void onUserInteraction() {
super.onUserInteraction();
isUserIntract = true;
}
I'm creating a spinner and I've added an OnItemSelectedListener to it.
However I've noticed that it fires on create.
Now I was wondering if there was a way to ignore/discard it.
I know I could use a boolean value, but that's a bit "dirty".
Here is my solution.
I need to ignore the first item selection event because there is a dependency between the Route Grade Spinner and the Route Checkbox.
And all my controls are setup based on a previous visit to the activity.
// Used to count the number of times the onItemSelected gets fired
private int mGradeSelectionCount = 0;
private void attachHandlers() {
OnItemSelectedListener gradeRangeSelectionMadeListener;
gradeRangeSelectionMadeListener = new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapter, View view, int position, long id) {
// If the counter is 0 then we can assume that it is android firing the event
if (mGradeSelectionCount++ < 1) {
return;
}
if (mCmbGradeFrom.getSelectedItemPosition() == 0) {
// Uncheck the Route checkbox
mChkTypeRoute.setChecked(false);
} else {
// Check the Route checkbox
mChkTypeRoute.setChecked(true);
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// Dont care, keep the same values as before
}
};
mCmbGradeFrom.setOnItemSelectedListener(gradeRangeSelectionMadeListener);
mChkTypeRoute.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!isChecked) {
mCmbGradeFrom.setSelection(0);
mCmbGradeTo.setSelection(0);
}
}
});
}
This may help you.
#Override
public void onItemSelected( AdapterView<?> parent, View view, int position, long id)
{
if(view!=null && view.getId()!=0){
//do your code here to avoid callback twice
}
}
You should not attempt to prevent the call to the OnItemSelectedListener.
By default, Android Spinners select the first item returned by the Adapter, and therefore the OnItemSelectedListener is called to trigger some action on that item.
I would advise that the first item in your Spinner Adapter be a blank item, and your OnItemSelectedListener can ignore that blank item based on its id.
If anyone else comes across this question, it may be worth having a look at a related question I asked a while ago, which has several answers with good ideas on how to work around this issue.
Well I think I found nice solution for me, I had it in mind from start but...
I have custom wrapper class based on android Handler , that is called DoLater, and also there is custom Adapter based on Listener so you cant copy paste this but you will get idea. Dangerous thing is just that somehow delay 500 can be to long and View can be already destroyed (when user do some wired stuff quickly or phone gets slow...) so DoLater cares of that so it is not called when activity is not resumed. But this way OnItemSelectedListener is not fired on create.
public void onResume() {
super.onResume();
new DoLater(this, 500) {
public void run() {
new OnSpinnerSelectedAdapter(getBowSpinner()) {
protected void onItemSelected(int position) {
onBowSelected(position);
}
};
}
};
}