why was the onNothingSelected in spinner not invoked? - android

I have an Android Spinner and I want to listen the event when the user press "Back Key" when the spinner's select panel is showing.I have implement the OnItemSelectedListener ,but the onNothingSelected(AdapterView arg0) was not invoked when press back key.
I just want to listen the event when user select nothing( or the select panel disappear) .
Is there a correct way to do this?
Thanks!
Spinner s1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.colors, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s1.setAdapter(adapter);
s1.setOnItemSelectedListener(
new OnItemSelectedListener() {
public void onItemSelected(
AdapterView<?> parent, View view, int position, long id) {
showToast("Spinner1: position=" + position + " id=" + id);
}
public void onNothingSelected(AdapterView<?> parent) {
showToast("Spinner1: unselected");
}
});
This is a sample in Android 2.2 SDK,it's also not show "Spinner1: unselected" when the select panel disappear.

It looks like you won't be able to do what you want without extending the Spinner class. It seems that Spinner doesn't register an OnCancelListener with the AlertDialog it builds to display the items.
Code from Spinner.java:
#Override
public boolean performClick() {
boolean handled = super.performClick();
if (!handled) {
handled = true;
Context context = getContext();
final DropDownAdapter adapter = new DropDownAdapter(getAdapter());
AlertDialog.Builder builder = new AlertDialog.Builder(context);
if (mPrompt != null) {
builder.setTitle(mPrompt);
}
mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show();
}
return handled;
}
public void onClick(DialogInterface dialog, int which) {
setSelection(which);
dialog.dismiss();
mPopup = null;
}
Also, setSelection is only called when an item in the dialog is clicked. This won't be called when the user presses the back button since that is an OnCancel event.
Extending Spinner will be a bit of a pain since you have to copy everything back to AdapterView into your source from the android source since various member fields necessary for implementation are only exposed at the package level.

Another approach is to create a minimal custom spinner dropdown item, ie:
<com.mypackage.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25dp"
/>
and then intercept onDetachedFromWindow():
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// Callback here
}
}
You can finesse this if you use a custom ArrayAdapter to set only one of the dropdown items to do the callback, as well as setting
suitable context for the callback, etc.
Depending on what you do inside the callback, you may want to
post it as a runnable, so that the spinner is fully cleaned up
before it does anything.

Related

How to set default text in spinner? [duplicate]

I want to use a Spinner that initially (when the user has not made a selection yet) displays the text "Select One". When the user clicks the spinner, the list of items is displayed and the user selects one of the options. After the user has made a selection, the selected item is displayed in the Spinner instead of "Select One".
I have the following code to create a Spinner:
String[] items = new String[] {"One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
With this code, initially the item "One" is displayed. I could just add a new item "Select One" to the items, but then "Select One" would also be displayed in the dropdown list as first item, which is not what I want.
How can I fix this problem?
What you can do is decorate your SpinnerAdapter with one that presents a 'Select Option...' View initially for the Spinner to display with nothing selected.
Here is a working example tested for Android 2.3, and 4.0 (it uses nothing in the compatibility library, so it should be fine for awhile) Since it's a decorator, it should be easy to retrofit existing code and it works fine with CursorLoaders also. (Swap cursor on the wrapped cursorAdapter of course...)
There is an Android bug that makes this a little tougher to re-use views. (So you have to use the setTag or something else to ensure your convertView is correct.) Spinner does not support multiple view types
Code notes: 2 constructors
This allows you to use a standard prompt or define your own 'nothing selected' as the first row, or both, or none. (Note: Some themes show a DropDown for a Spinner instead of a dialog. The Dropdown doesn't normally show the prompt)
You define a layout to 'look' like a prompt, for example, grayed out...
Using a standard prompt (notice that nothing is selected):
Or with a prompt and something dynamic (could have had no prompt also):
Usage in above example
Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.planets_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setPrompt("Select your favorite Planet!");
spinner.setAdapter(
new NothingSelectedSpinnerAdapter(
adapter,
R.layout.contact_spinner_row_nothing_selected,
// R.layout.contact_spinner_nothing_selected_dropdown, // Optional
this));
contact_spinner_row_nothing_selected.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textSize="18sp"
android:textColor="#808080"
android:text="[Select a Planet...]" />
NothingSelectedSpinnerAdapter.java
import android.content.Context;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;
/**
* Decorator Adapter to allow a Spinner to show a 'Nothing Selected...' initially
* displayed instead of the first choice in the Adapter.
*/
public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {
protected static final int EXTRA = 1;
protected SpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
/**
* Use this constructor to have NO 'Select One...' item, instead use
* the standard prompt or nothing at all.
* #param spinnerAdapter wrapped Adapter.
* #param nothingSelectedLayout layout for nothing selected, perhaps
* you want text grayed out like a prompt...
* #param context
*/
public NothingSelectedSpinnerAdapter(
SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, Context context) {
this(spinnerAdapter, nothingSelectedLayout, -1, context);
}
/**
* Use this constructor to Define your 'Select One...' layout as the first
* row in the returned choices.
* If you do this, you probably don't want a prompt on your spinner or it'll
* have two 'Select' rows.
* #param spinnerAdapter wrapped Adapter. Should probably return false for isEnabled(0)
* #param nothingSelectedLayout layout for nothing selected, perhaps you want
* text grayed out like a prompt...
* #param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
* the dropdown.
* #param context
*/
public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.from(context);
}
#Override
public final View getView(int position, View convertView, ViewGroup parent) {
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0) {
return getNothingSelectedView(parent);
}
return adapter.getView(position - EXTRA, null, parent); // Could re-use
// the convertView if possible.
}
/**
* View to show in Spinner with Nothing Selected
* Override this to do something dynamic... e.g. "37 Options Found"
* #param parent
* #return
*/
protected View getNothingSelectedView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedLayout, parent, false);
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0) {
return nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
/**
* Override this to do something dynamic... For example, "Pick your favorite
* of these 37".
* #param parent
* #return
*/
protected View getNothingSelectedDropdownView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
}
#Override
public int getCount() {
int count = adapter.getCount();
return count == 0 ? 0 : count + EXTRA;
}
#Override
public Object getItem(int position) {
return position == 0 ? null : adapter.getItem(position - EXTRA);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return position >= EXTRA ? adapter.getItemId(position - EXTRA) : position - EXTRA;
}
#Override
public boolean hasStableIds() {
return adapter.hasStableIds();
}
#Override
public boolean isEmpty() {
return adapter.isEmpty();
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
adapter.registerDataSetObserver(observer);
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
adapter.unregisterDataSetObserver(observer);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return position != 0; // Don't allow the 'nothing selected'
// item to be picked.
}
}
Here's a general solution that overrides the Spinner view. It overrides setAdapter() to set the initial position to -1, and proxies the supplied SpinnerAdapter to display the prompt string for position less than 0.
This has been tested on Android 1.5 through 4.2, but buyer beware! Because this solution relies on reflection to call the private AdapterView.setNextSelectedPositionInt() and AdapterView.setSelectedPositionInt(), it's not guaranteed to work in future OS updates. It seems likely that it will, but it is by no means guaranteed.
Normally I wouldn't condone something like this, but this question has been asked enough times and it seems like a reasonable enough request that I thought I would post my solution.
/**
* A modified Spinner that doesn't automatically select the first entry in the list.
*
* Shows the prompt if nothing is selected.
*
* Limitations: does not display prompt if the entry list is empty.
*/
public class NoDefaultSpinner extends Spinner {
public NoDefaultSpinner(Context context) {
super(context);
}
public NoDefaultSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoDefaultSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setAdapter(SpinnerAdapter orig ) {
final SpinnerAdapter adapter = newProxy(orig);
super.setAdapter(adapter);
try {
final Method m = AdapterView.class.getDeclaredMethod(
"setNextSelectedPositionInt",int.class);
m.setAccessible(true);
m.invoke(this,-1);
final Method n = AdapterView.class.getDeclaredMethod(
"setSelectedPositionInt",int.class);
n.setAccessible(true);
n.invoke(this,-1);
}
catch( Exception e ) {
throw new RuntimeException(e);
}
}
protected SpinnerAdapter newProxy(SpinnerAdapter obj) {
return (SpinnerAdapter) java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
new Class[]{SpinnerAdapter.class},
new SpinnerAdapterProxy(obj));
}
/**
* Intercepts getView() to display the prompt if position < 0
*/
protected class SpinnerAdapterProxy implements InvocationHandler {
protected SpinnerAdapter obj;
protected Method getView;
protected SpinnerAdapterProxy(SpinnerAdapter obj) {
this.obj = obj;
try {
this.getView = SpinnerAdapter.class.getMethod(
"getView",int.class,View.class,ViewGroup.class);
}
catch( Exception e ) {
throw new RuntimeException(e);
}
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
try {
return m.equals(getView) &&
(Integer)(args[0])<0 ?
getView((Integer)args[0],(View)args[1],(ViewGroup)args[2]) :
m.invoke(obj, args);
}
catch (InvocationTargetException e) {
throw e.getTargetException();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
protected View getView(int position, View convertView, ViewGroup parent)
throws IllegalAccessException {
if( position<0 ) {
final TextView v =
(TextView) ((LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE)).inflate(
android.R.layout.simple_spinner_item,parent,false);
v.setText(getPrompt());
return v;
}
return obj.getView(position,convertView,parent);
}
}
}
I ended up using a Button instead. While a Button is not a Spinner, the behavior is easy to customize.
First create the Adapter as usual:
String[] items = new String[] {"One", "Two", "Three"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_dropdown_item, items);
Note that I am using the simple_spinner_dropdown_item as the layout id. This will help create a better look when creating the alert dialog.
In the onClick handler for my Button I have:
public void onClick(View w) {
new AlertDialog.Builder(this)
.setTitle("the prompt")
.setAdapter(adapter, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// TODO: user specific action
dialog.dismiss();
}
}).create().show();
}
And that's it!
First, you might be interested in the prompt attribute of the Spinner class. See the picture below, "Choose a Planet" is the prompt that can be set in the XML with android:prompt="".
I was going to suggest subclassing Spinner, where you could maintain two adapters internally. One adapter that has the "Select One" option, and the other real adapter (with the actual options), then using the OnClickListener to switch the adapters before the choices dialog is shown. However, after trying implement that idea I've come to the conclusion you cannot receive OnClick events for the widget itself.
You could wrap the spinner in a different view, intercept the clicks on the view, and then tell your CustomSpinner to switch the adapter, but seems like an awful hack.
Do you really need to show "Select One"?
This code has been tested and works on Android 4.4
Spinner spinner = (Spinner) activity.findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_dropdown_item) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
if (position == getCount()) {
((TextView)v.findViewById(android.R.id.text1)).setText("");
((TextView)v.findViewById(android.R.id.text1)).setHint(getItem(getCount())); //"Hint to be displayed"
}
return v;
}
#Override
public int getCount() {
return super.getCount()-1; // you dont display last item. It is used as hint.
}
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.add("Daily");
adapter.add("Two Days");
adapter.add("Weekly");
adapter.add("Monthly");
adapter.add("Three Months");
adapter.add("HINT_TEXT_HERE"); //This is the text that will be displayed as hint.
spinner.setAdapter(adapter);
spinner.setSelection(adapter.getCount()); //set the hint the default selection so it appears on launch.
spinner.setOnItemSelectedListener(this);
I found this solution:
String[] items = new String[] {"Select One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
items[0] = "One";
selectedItem = items[position];
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
Just change the array[0] with "Select One" and then in the onItemSelected, rename it to "One".
Not a classy solution, but it works :D
Lots of answers here but I'm surprised no one suggested a simple solution: Place a TextView on top of the Spinner. Set a click listener on the TextView which hides the TextView shows the Spinner, and calls spinner.performClick().
There is no default API to set hint on Spinner. To add it we need a small workaround with out that not safety reflection implementation
List<Object> objects = new ArrayList<Object>();
objects.add(firstItem);
objects.add(secondItem);
// add hint as last item
objects.add(hint);
HintAdapter adapter = new HintAdapter(context, objects, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner spinnerFilmType = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);
// show hint
spinner.setSelection(adapter.getCount());
Adapter source:
public class HintAdapter
extends ArrayAdapter<Objects> {
public HintAdapter(Context theContext, List<Object> objects) {
super(theContext, android.R.id.text1, android.R.id.text1, objects);
}
public HintAdapter(Context theContext, List<Object> objects, int theLayoutResId) {
super(theContext, theLayoutResId, android.R.id.text1, objects);
}
#Override
public int getCount() {
// don't display last item. It is used as hint.
int count = super.getCount();
return count > 0 ? count - 1 : count;
}
}
Original source
I got the same problem for spinner, with an empty selection, and I found a better solution. Have a look at this simple code.
Spinner lCreditOrDebit = (Spinner)lCardPayView.findViewById(R.id.CARD_TYPE);
spinneradapter lAdapter =
new spinneradapter(
BillPayScreen.this,
ndroid.R.layout.simple_spinner_item,getResources().getStringArray(R.array.creditordebit));
lAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
lCreditOrDebit.setAdapter(lAdapter);
Here spinneradapter is a small customization for arrayadapter. It looks like this:
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
public class spinneradapter extends ArrayAdapter<String>{
private Context m_cContext;
public spinneradapter(Context context,int textViewResourceId, String[] objects) {
super(context, textViewResourceId, objects);
this.m_cContext = context;
}
boolean firsttime = true;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(firsttime){
firsttime = false;
//Just return some empty view
return new ImageView(m_cContext);
}
//Let the array adapter take care of it this time.
return super.getView(position, convertView, parent);
}
}
You can change it to a Text View and use this:
android:style="#android:style/Widget.DeviceDefault.Light.Spinner"
and then define the android:text property.
XML file:
<Spinner android:id="#+id/locationSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="#string/select_location" />
Activity:
private Spinner featuresSelection;
private ArrayAdapter<CharSequence> featuresAdapter;
private List<CharSequence> featuresList;
onCreate:
featuresList = new ArrayList<CharSequence>();
featuresAdapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_spinner_item, featuresList);
featuresAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
featuresSelection = ((Spinner) yourActivity.this
.findViewById(R.id.locationSpinner));
featuresSelection.setAdapter(featuresAdapter);
featuresSelection.setOnItemSelectedListener(
new MyOnItemSelectedListener());
Some function (add things to the adapter programmatically)>
featuresAdapter.add("some string");
Now you have an empty spinner and you can write code to not open the dialog if empty. Or they can press back. But you also populate it with a function or another list during run time.
In addition, There is a simple trick to show default:
You can add a default value in your list and then add all of your collection using list.addAll(yourCollection);
Sample workable code here:
List<FuelName> fuelList = new ArrayList<FuelName>();
fuelList.add(new FuelName(0,"Select One"));
fuelList.addAll(response.body());
ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, fuelList);
//fuelName.setPrompt("Select Fuel");
fuelName.setAdapter(adapter);
I have a spinner on my main.xml and its id is #+id/spinner1
this is what i write in my OnCreate function :
spinner1 = (Spinner)this.findViewById(R.id.spinner1);
final String[] groupes = new String[] {"A", "B", "C", "D", "E", "F", "G", "H"};
ArrayAdapter<CharSequence> featuresAdapter = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_item, new ArrayList<CharSequence>());
featuresAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner1.setAdapter(featuresAdapter);
for (String s : groupes) featuresAdapter.add(s);
spinner1.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
// Here go your instructions when the user chose something
Toast.makeText(getBaseContext(), groupes[position], 0).show();
}
public void onNothingSelected(AdapterView<?> arg0) { }
});
It doesn't need any implementation in the class.
I have tried like the following. Take a button and give the click event to it. By changing the button background, it seems to be a spinner.
Declare as global variables alertdialog and default value..
AlertDialog d;
static int default_value = 0;
final Button btn = (Button) findViewById(R.id.button1);
btn .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
//c.show();
final CharSequence str[] = {"Android","Black Berry","Iphone"};
AlertDialog.Builder builder =
new AlertDialog.Builder(TestGalleryActivity.this).setSingleChoiceItems(
str, default_value,new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int position)
{
Toast.makeText(TestGalleryActivity.this,
"" + position,
Toast.LENGTH_SHORT).show();
default_value = position;
btn.setText(str[position]);
if(d.isShowing())
d.dismiss();
}
}).setTitle("Select Any");
d = builder.create();
d.show();
}
});
Take a look at the iosched app for a general purpose solution to adding an element to the top of a list. In particular, if you are using a CursorAdapter, look at TracksAdapter.java which extends that definition to provide a "setHasAllItem" method and associated code to manage the list count to deal with the extra item at the top.
Using the custom adapter you can set the text to "Select One" or whatever else you may want that top item to say.
I found many good solutions for this. most is working by adding an item to the end of adapter, and don't display the last item in drop-down list.
The big problem for me was the spinner drop-down list will start from the bottom of the list. So user see the last items instead of the first items (in case of have many items to show), after touch the spinner for the first time.
So I put the hint item to the beginning of the list. and hide the first item in drop-down list.
private void loadSpinner(){
HintArrayAdapter hintAdapter = new HintArrayAdapter<String>(context, 0);
hintAdapter.add("Hint to be displayed");
hintAdapter.add("Item 1");
hintAdapter.add("Item 2");
.
.
hintAdapter.add("Item 30");
spinner1.setAdapter(hintAdapter);
//spinner1.setSelection(0); //display hint. Actually you can ignore it, because the default is already 0
//spinner1.setSelection(0, false); //use this if don't want to onItemClick called for the hint
spinner1.setOnItemSelectedListener(yourListener);
}
private class HintArrayAdapter<T> extends ArrayAdapter<T> {
Context mContext;
public HintArrayAdapter(Context context, int resource) {
super(context, resource);
this.mContext = context
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
TextView texview = (TextView) view.findViewById(android.R.id.text1);
if(position == 0) {
texview.setText("");
texview.setHint(getItem(position).toString()); //"Hint to be displayed"
} else {
texview.setText(getItem(position).toString());
}
return view;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view;
if(position == 0){
view = inflater.inflate(R.layout.spinner_hint_list_item_layout, parent, false); // Hide first row
} else {
view = inflater.inflate(android.R.layout.simple_spinner_dropdown_item, parent, false);
TextView texview = (TextView) view.findViewById(android.R.id.text1);
texview.setText(getItem(position).toString());
}
return view;
}
}
set the below layout in #Override getDropDownView() when position is 0, to hide the first hint row.
R.layout.spinner_hint_list_item_layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</LinearLayout>
I think the easiest way is creating a dummy item on index 0 saying "select one" and then on saving maybe check that selection is not 0.
So this is my final example "all-in" for a button-spinner
In activity_my_form.xml
<Button
android:id="#+id/btnSpinnerPlanets"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:singleLine="true"
android:text="#string/selectAPlanet"
android:textSize="10sp"
android:background="#android:drawable/btn_dropdown">
</Button>
In strings.xml
<string name="selectAPlanet">Select planet…</string>
<string-array name="planets__entries">
<item>The Sun with a name very long so long long long long longThe Sun with a name very long so long long long long longThe Sun with a name very long so long long long long long</item>
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
<item>Mars</item>
<item>Jupiter</item>
<item>Saturn</item>
<item>Uranus</item>
<item>Neptune</item>
</string-array>
In MyFormActivity.java
public class MyFormActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
((Button) findViewById(R.id.btnSpinnerPlanets)).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final String[] items = view.getResources().getStringArray(R.array.planets__entries);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MyFormActivity.this, android.R.layout.simple_spinner_dropdown_item, items);
new AlertDialog.Builder(MyFormActivity.this).setTitle("the prompt").setAdapter(adapter, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((Button) findViewById(R.id.btnSpinnerPlanets)).setText(items[which]);
dialog.dismiss();
}
}).create().show();
}
});
}
}
Finally I obtained a font size configurable no first item selectable button spinner!!!
Thanks to HRJ
The best solution I found for this is actually not to use a Spinner but an AutoCompleteTextView. Its basically an EditText with attached Spinner to show suggestions as you type - but, with the right config, it can behave exactly as the OP wishes and more.
XML:
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatAutoCompleteTextView
android:id="#+id/input"
android:hint="Select one"
style="#style/AutoCompleteTextViewDropDown"/>
</com.google.android.material.textfield.TextInputLayout>
Style:
<style name="AutoCompleteTextViewDropDown">
<item name="android:clickable">false</item>
<item name="android:cursorVisible">false</item>
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
As for the adapter use the basic ArrayAdapter or extend it to make your own, but no additional customization on the adapter side is necessary. Set the adapter on the AutoCompleteTextView.
When extending SpinnerAdapter, you override two View-producing methods, getView(int, View, ViewGroup) and getDropDownView(int, View, ViewGroup). The first one supplies the View inserted into the Spinner itself; the second supplies the View in the drop-down list (as the name suggests). You can override the getView(...) so that, until an item has been selected, it displays a TextView containing a prompt; then, when you detect an item has been selected, you change it to display a TextView corresponding to that.
public class PromptingAdapter extends SpinnerAdapter {
//... various code ...
private boolean selectionmade = false;
//call this method from the OnItemSelectedListener for your Spinner
public setSelectionState(boolean b) {
selectionmade = b;
}
#Override
public View getView(int position, View recycle, ViewGroup container) {
if(selectionmade) {
//your existing code to supply a View for the Spinner
//you could even put "return getDropDownView(position, recycle, container);"
}
else {
View output;
if(recycle instanceof TextView) {
output = recycle;
}
else {
output = new TextView();
//and layout stuff
}
output.setText(R.string.please_select_one);
//put a string "please_select_one" in res/values/strings.xml
return output;
}
}
//...
}
For those using Xamarin, here is the C# equivalent to aaronvargas's answer above.
using Android.Content;
using Android.Database;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace MyNamespace.Droid
{
public class NothingSelectedSpinnerAdapter : BaseAdapter, ISpinnerAdapter, IListAdapter
{
protected static readonly int EXTRA = 1;
protected ISpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
public NothingSelectedSpinnerAdapter(ISpinnerAdapter spinnerAdapter, int nothingSelectedLayout, Context context) : this(spinnerAdapter, nothingSelectedLayout, -1, context)
{
}
public NothingSelectedSpinnerAdapter(ISpinnerAdapter spinnerAdapter, int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context)
{
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.From(context);
}
protected View GetNothingSelectedView(ViewGroup parent)
{
return layoutInflater.Inflate(nothingSelectedLayout, parent, false);
}
protected View GetNothingSelectedDropdownView(ViewGroup parent)
{
return layoutInflater.Inflate(nothingSelectedDropdownLayout, parent, false);
}
public override Object GetItem(int position)
{
return position == 0 ? null : adapter.GetItem(position - EXTRA);
}
public override long GetItemId(int position)
{
return position >= EXTRA ? adapter.GetItemId(position - EXTRA) : position - EXTRA;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0)
{
return GetNothingSelectedView(parent);
}
// Could re-use the convertView if possible.
return this.adapter.GetView(position - EXTRA, null, parent);
}
public override int Count
{
get
{
int count = this.adapter.Count;
return count == 0 ? 0 : count + EXTRA;
}
}
public override View GetDropDownView(int position, View convertView, ViewGroup parent)
{
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0)
{
return nothingSelectedDropdownLayout == -1 ?
new View(context) :
GetNothingSelectedDropdownView(parent);
}
// Could re-use the convertView if possible, use setTag...
return adapter.GetDropDownView(position - EXTRA, null, parent);
}
public override int GetItemViewType(int position)
{
return 0;
}
public override int ViewTypeCount => 1;
public override bool HasStableIds => this.adapter.HasStableIds;
public override bool IsEmpty => this.adapter.IsEmpty;
public override void RegisterDataSetObserver(DataSetObserver observer)
{
adapter.RegisterDataSetObserver(observer);
}
public override void UnregisterDataSetObserver(DataSetObserver observer)
{
adapter.UnregisterDataSetObserver(observer);
}
public override bool AreAllItemsEnabled()
{
return false;
}
public override bool IsEnabled(int position)
{
return position > 0;
}
}
}
I also solved this problem by using the following code. Suppose you are having a list of items e.g.
ArrayList<Item> itemsArrayList = new ArrayList<Item>();
Item item1 = new Item();
item1.setId(1);
item1.setData("First Element");
Item item2 = new Item();
item2.setId(2);
Item2.setData("Second Element");
itemsArrayList.add(item1);
itemsArrayList.add(item2);
Now we have to provide the strings to spinner because spinner can not understand the object. So we will create an new array list with string items like this ->
ArrayList<String> itemStringArrayList = new ArrayList<String>();
for(Item item : itemsArrayList) {
itemStringArrayList.add(item.getData());
}
Now we have itemStringArrayList array list with two string items. And we have to show the "Select Item" text as first item. So we have to insert an new string into the itemStringArrayList.
itemStringArrayList.add("Select Item");
Now we have an array list itemsArrayList and we want to show two elements in the drop down. But the condition here is ...If we don't select anything then Select Item should appear as first element which will not be enabled.
So we can implement this functionality like this. If you need to load the array list items into the android spinner. So you will have to use some adapter. So here i'll use the ArrayAdapter. We can use the customise adapter too.
ArrayAdapter<String> itemsArrayAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.spinner_item, itemsArrayList){
#Override
public boolean isEnabled(int position) {
if(position == 0)
{
return false;
}
else
{
return true;
}
}
#Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
View view = super.getDropDownView(position, convertView, parent);
TextView tv = (TextView) view;
if(position == 0){
// Set the hint text color gray
tv.setTextColor(Color.GRAY);
}
else {
tv.setTextColor(Color.BLACK);
}
return view;
}
};
itemsArrayAdapter.setDropDownViewResource(R.layout.spinner_item);
your_spinner_name.setAdapter(itemsArrayAdapter);
Here in this code. we are using the customised spinner layout i.e. R.layout.spinner_item. It's a simple text view
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textStyle="italic"
android:fontFamily="sans-serif-medium"
/>
We need to disable the first text in the spinner. So for the position 0 we are disabling the text. And color also we can set by overiding getDropDownView method. So in this way we will get the expected spinner.
I was facing the same problem yesterday and did not want to add a hidden item to the ArrayAdapter or use reflections, which works fine but is kind of dirty.
After reading many posts and trying around I found a solution by extending ArrayAdapter and Overriding the getView method.
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
/**
* A SpinnerAdapter which does not show the value of the initial selection initially,
* but an initialText.
* To use the spinner with initial selection instead call notifyDataSetChanged().
*/
public class SpinnerAdapterWithInitialText<T> extends ArrayAdapter<T> {
private Context context;
private int resource;
private boolean initialTextWasShown = false;
private String initialText = "Please select";
/**
* Constructor
*
* #param context The current context.
* #param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* #param objects The objects to represent in the ListView.
*/
public SpinnerAdapterWithInitialText(#NonNull Context context, int resource, #NonNull T[] objects) {
super(context, resource, objects);
this.context = context;
this.resource = resource;
}
/**
* Returns whether the user has selected a spinner item, or if still the initial text is shown.
* #param spinner The spinner the SpinnerAdapterWithInitialText is assigned to.
* #return true if the user has selected a spinner item, false if not.
*/
public boolean selectionMade(Spinner spinner) {
return !((TextView)spinner.getSelectedView()).getText().toString().equals(initialText);
}
/**
* Returns a TextView with the initialText the first time getView is called.
* So the Spinner has an initialText which does not represent the selected item.
* To use the spinner with initial selection instead call notifyDataSetChanged(),
* after assigning the SpinnerAdapterWithInitialText.
*/
#Override
public View getView(int position, View recycle, ViewGroup container) {
if(initialTextWasShown) {
return super.getView(position, recycle, container);
} else {
initialTextWasShown = true;
LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(resource, container, false);
((TextView) view).setText(initialText);
return view;
}
}
}
What Android does when initialising the Spinner, is calling getView for the selected item before calling getView for all items in T[] objects.
The SpinnerAdapterWithInitialText returns a TextView with the initialText, the first time it is called.
All the other times it calls super.getView which is the getView method of ArrayAdapter which is called if you are using the Spinner normally.
To find out whether the user has selected a spinner item, or if the spinner still displays the initialText, call selectionMade and hand over the spinner the adapter is assigned to.
See the answer with lightweight and high customisable library:
https://stackoverflow.com/a/73085435/6694920
<com.innowisegroup.hintedspinner.HintedSpinner
android:id="#+id/hintedSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
app:hintTextSize="24sp"
app:hintTextColor="#color/red"
app:hint="Custom hint"
app:withDivider="true"
app:dividerColor="#color/dark_green"
app:arrowDrawable="#drawable/example_arrow_4"
app:arrowTint="#color/colorAccent"
app:popupBackground="#color/light_blue"
app:items="#array/text" />
Collapsed spinner:
Expanded spinner:
I'd just use a RadioGroup with RadioButtons if you only have three choices, you can make them all unchecked at first.
None of the previously submitted answers really worked the way I wanted to solve this issue. To me the ideal solution would provide the “Select One” (or whatever initial text) when the spinner is first displayed. When the user taps the spinner, the initial text shouldn’t be a part of the drop down that is displayed.
To further complicate my particular situation, my spinner data is coming form a cursor that is loaded via the LoaderManager callbacks.
After considerable experimentation I came up with the following solution:
public class MyFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>{
private static final String SPINNER_INIT_VALUE = "Select A Widget";
private Spinner mSpinner;
private int mSpinnerPosition;
private boolean mSpinnerDropDownShowing = false;
private View mSpinnerDropDown;
private MyCursorAdapter mCursorAdapter;
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
...
mCursorAdapter = new MyCursorAdapter(getActivity());
mSpinner = (Spinner) rootView.findViewById(R.id.theSpinner);
mSpinner.setOnTouchListener(mSpinnerTouchListener);
mSpinner.setAdapter(mCursorAdapter);
...
}
//Capture the touch events to toggle the spinner's dropdown visibility
private OnTouchListener mSpinnerTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if(mSpinnerDropDown != null && mSpinnerDropDownShowing == false){
mSpinnerDropDownShowing = true;
mSpinnerDropDown.setVisibility(View.VISIBLE);
}
return false;
}
};
//Capture the click event on the spinner drop down items
protected OnClickListener spinnerItemClick = new OnClickListener(){
#Override
public void onClick(View view) {
String widget = ((TextView) view.findViewById(android.R.id.text1)).getText().toString();
if(!widget.equals(SPINNER_INIT_VALUE)){
if(mCursorAdapter != null){
Cursor cursor = mCursorAdapter.getCursor();
if(cursor.moveToFirst()){
while(!cursor.isAfterLast()){
if(widget.equals(cursor.getString(WidgetQuery.WIDGET_NAME))){
...
//Set the spinner to the correct item
mSpinnerPosition = cursor.getPosition() + 1;
mSpinner.setSelection(mSpinnerPosition);
break;
}
cursor.moveToNext();
}
}
}
}
//Hide the drop down. Not the most elegent solution but it is the only way I could hide/dismiss the drop down
mSpinnerDropDown = view.getRootView();
mSpinnerDropDownShowing = false;
mSpinnerDropDown.setVisibility(View.GONE);
}
};
private class MyCursorAdapter extends CursorAdapter {
private final int DISPLACEMENT = 1;
private final int DEFAULT_ITEM_ID = Integer.MAX_VALUE;
private Activity mActivity;
public MyCursorAdapter(Activity activity) {
super(activity, null, false);
mActivity = activity;
}
//When loading the regular views, inject the defualt item
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position == 0){
if(convertView == null){
convertView = mActivity.getLayoutInflater().inflate(R.layout.list_item_widget, parent, false);
}
return getDefaultItem(convertView);
}
return super.getView(position - DISPLACEMENT, convertView, parent);
}
//When loading the drop down views, set the onClickListener for each view
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent){
View view = super.getDropDownView(position, convertView, parent);
view.setOnClickListener(spinnerItemClick);
return view;
}
//The special default item that is being injected
private View getDefaultItem(View convertView){
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
text.setText(SPINNER_INIT_VALUE);
return convertView;
}
#Override
public long getItemId(int position) {
if (position == 0) {
return DEFAULT_ITEM_ID;
}
return super.getItemId(position - DISPLACEMENT);
}
#Override
public boolean isEnabled(int position) {
return position == 0 ? true : super.isEnabled(position - DISPLACEMENT);
}
#Override
public int getViewTypeCount() {
return super.getViewTypeCount() + DISPLACEMENT;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return super.getViewTypeCount();
}
return super.getItemViewType(position - DISPLACEMENT);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mActivity.getLayoutInflater().inflate(R.layout.list_item_widget, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor){
if(cursor.isAfterLast()){
return;
}
TextView text = (TextView) view.findViewById(android.R.id.text1);
String WidgetName = cursor.getString(WidgetQuery.WIDGET_NAME);
text.setText(WidgetName);
}
}
}
I handle this by using a button instead of a Spinner. I have the sample project up on GitHub.
In the project, i'm displaying both the Spinner and button to show that they indeed look identical. Except the button you can set the initial text to whatever you want.
Here's what the activity looks like:
package com.stevebergamini.spinnerbutton;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Spinner;
public class MainActivity extends Activity {
Spinner spinner1;
Button button1;
AlertDialog ad;
String[] countries;
int selected = -1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
spinner1 = (Spinner) findViewById(R.id.spinner1);
button1 = (Button) findViewById(R.id.button1);
countries = getResources().getStringArray(R.array.country_names);
// You can also use an adapter for the allert dialog if you'd like
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, countries);
ad = new AlertDialog.Builder(MainActivity.this).setSingleChoiceItems(countries, selected,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
button1.setText(countries[which]);
selected = which;
ad.dismiss();
}}).setTitle(R.string.select_country).create();
button1.setOnClickListener( new OnClickListener(){
#Override
public void onClick(View v) {
ad.getListView().setSelection(selected);
ad.show();
}});
}
}
NOTE: Yes, I realize that this is dependent on the applied Theme and the look will be slightly different if using Theme.Holo. However, if you're using one of the legacy themes such as Theme.Black, you're good to go.
Seems a banal solution but I usually put simply a TextView in the front of the spinner. The whole Xml looks like this. (hey guys, don't shoot me, I know that some of you don't like this kind of marriage):
<FrameLayout
android:id="#+id/selectTypesLinear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Spinner
android:id="#+id/spinnerExercises"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:entries="#array/exercise_spinner_entries"
android:prompt="#string/exercise_spinner_prompt"
/>
<TextView
android:id="#+id/spinnerSelectText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hey! Select this guy!"
android:gravity="center"
android:background="#FF000000" />
</FrameLayout>
Then I hide the TextView when an Item was selected. Obviously the background color of the TextView should be the same as the Spinner. Works on Android 4.0. Don't know on older versions.
Yes. Because the Spinner calls setOnItemSelectedListener at the beginning, the hiding of the textview could be a little bit tricky, but can be done this way:
Boolean controlTouched;
exerciseSpinner.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
controlTouched = true; // I touched it but but not yet selected an Item.
return false;
}
});
exerciseSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if (controlTouched) { // Are you sure that I touched it with my fingers and not someone else ?
spinnerSelText.setVisibility(View.GONE);
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
for me it worked something like this. has the improvement that only changes the text in SOME options, not in all.
First i take the names of the spinner and create the arrayadapter with a customize view, but it doesn't matter now, the key is override the getView, and inside change the values u need to change. In my case was only the first one, the rest i leave the original
public void rellenarSpinnerCompeticiones(){
spinnerArrayCompeticiones = new ArrayList<String>();
for(Competicion c: ((Controlador)getApplication()).getCompeticiones()){
spinnerArrayCompeticiones.add(c.getNombre());
}
//ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this,R.layout.spinner_item_competicion,spinnerArrayCompeticiones);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this, R.layout.spinner_item_competicion, spinnerArrayCompeticiones){
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View v = vi.inflate(R.layout.spinner_item_competicion, null);
final TextView t = (TextView)v.findViewById(R.id.tvCompeticion);
if(spinnerCompeticion.getSelectedItemPosition()>0){
t.setText(spinnerArrayCompeticiones.get(spinnerCompeticion.getSelectedItemPosition()));
}else{
t.setText("Competiciones");
}
return v;
}
};
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCompeticion.setAdapter(spinnerArrayAdapter);
}
Refer to one of the above answers: https://stackoverflow.com/a/23005376/1312796
I added my code to fix a little bug. That where no data retrieved..How to show the prompt text..!
Here is my Trick...It works fine with me. !
Try to put your spinner in a Relative_layoutand align a Textview with your spinner and play with the visibility of the Textview (SHOW/HIDE) whenever the adapter of the spinner loaded or empty..Like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:background="#ededed"
android:orientation="vertical">
<TextView
android:id="#+id/txt_prompt_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="#color/gray"
android:textSize="16sp"
android:layout_alignStart="#+id/sp_from"
android:text="From"
android:visibility="gone"/>
<Spinner
android:id="#+id/sp_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
/>
Here is the code:
txt__from = (TextView) rootView.findViewById(R.id.txt_prompt_from);
call this method after and before spinner adapter loaded and empty.
setPromptTextViewVisibility (); //True or fales
public void setPromptTextViewVisibility (boolean visible )
{
if (visible)
{
txt_from.setVisibility(View.VISIBLE);
}
else
{
txt_from.setVisibility(View.INVISIBLE);
}
}

How to find whether if user clicked spinner in Android?

In my application I am creating spinner dynamically in code.I want to force user to click on Spinner and change its value/content. Otherwise user should not be able to go to next screen by clicking Next button.
How to do that in Android? Anybody has any idea?
Thanks in Advance.
Rohan
you can use this:
if (spin.getSelectedItemPosition() < 0) {//Do something}
this means user hasn't selected anything.
Let the Spinner's first value be something like "-please select-".
when the user clicks on the next button perform validation and check whether the value of the selectedItem in the spinner is "-please select-" and if yes, then display a toast and ask the the user to select something from the spinner.
you need code, let me know.
Use this code for checking the spinner item is selected or not.
both flags take at class level (Globally).
Boolean temp = false;
Boolean check = false;
spinner1.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
if(temp){
check = true;
}
temp = true;
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
check = false;
}
});
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(check){
//perform when user select spinner item
}else{
//put Dialog for alert please select spinner item
}
}
}
Call your Intent for the next Activity in the Click Listener of that spinner
Create one boolean global variable like..
boolean isSelect = false;
Now when user select value from spinner then make it isSelect = false. And when user click on NEXT button check condition for isSelect is true or false.
That's it.
You can get the selected item value by using the following method.
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if (spinner.getSelectedItem().toString().equals("YourValue")) {
Intent yourIntent = new Intent(this,YourClassName.class);
startActivity(yourIntent);
}
}
public void onNothingSelected(AdapterView<?> arg0) {
}
});
In my case I added an extra item on first position of the list
1."Select Something"
2."Go next"
3."Go Previous"
5....
6..
then in use
String item=spinnerObject.getSelectedItem ();
now check if("Select Something".equels(item)){
show some dialog to select anything from spinner
}else{
send it to next screen
}
I made a new Spinner class encapsulating the above mentioned principles. But even then you have to make sure to call the correct method and not setSelection
Same thing in a gist
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
/**
* Used this to differentiate between user selected and prorammatically selected
* Call {#link Spinner#programmaticallySetPosition} to use this feature.
* Created by vedant on 6/1/15.
*/
public class Spinner extends android.widget.Spinner implements AdapterView.OnItemSelectedListener {
OnItemSelectedListener mListener;
/**
* used to ascertain whether the user selected an item on spinner (and not programmatically)
*/
private boolean mUserActionOnSpinner = true;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (mListener != null) {
mListener.onItemSelected(parent, view, position, id, mUserActionOnSpinner);
}
// reset variable, so that it will always be true unless tampered with
mUserActionOnSpinner = true;
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (mListener != null)
mListener.onNothingSelected(parent);
}
public interface OnItemSelectedListener {
/**
* <p>Callback method to be invoked when an item in this view has been
* selected. This callback is invoked only when the newly selected
* position is different from the previously selected position or if
* there was no selected item.</p>
*
* Impelmenters can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* #param parent The AdapterView where the selection happened
* #param view The view within the AdapterView that was clicked
* #param position The position of the view in the adapter
* #param id The row id of the item that is selected
*/
void onItemSelected(AdapterView<?> parent, View view, int position, long id, boolean userSelected);
/**
* Callback method to be invoked when the selection disappears from this
* view. The selection can disappear for instance when touch is activated
* or when the adapter becomes empty.
*
* #param parent The AdapterView that now contains no selected item.
*/
void onNothingSelected(AdapterView<?> parent);
}
public void programmaticallySetPosition(int pos, boolean animate) {
mUserActionOnSpinner = false;
setSelection(pos, animate);
}
public void setOnItemSelectedListener (OnItemSelectedListener listener) {
mListener = listener;
}
public Spinner(Context context) {
super(context);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, int mode) {
super(context, mode);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setOnItemSelectedListener(this);
}
public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
super(context, attrs, defStyle, mode);
super.setOnItemSelectedListener(this);
}
}

Why is OnItemSelectedListener only called when an item changes, but not on every user selection?

I'm using spinner controls in an Android application, and I handle user selections via an onItemSelectedListener() method. This seems to work okay when a different selection from the current one is made. I would like, under certain conditions to reset all spinners to default values and ensure that onItemSelectedListener() is called for all.
Is it part of Android's semantics that onItemSelectedListener() is only called when user selection changes. Is there a way to force onItemSelectedListener() to be called?
If you want "onItemSelected" of Spinner to be fired, even if the item in the spinner is selected /if item selected and it is clicked again .
Then use this custom class which extends spinner ,this worked for me
Then edit your activity with spinners like this , I changed
static Spinner spinner1;
to
static NDSpinner spinner1;
and
variables.spinner1 = (Spinner) findViewById(R.id.spinner1);
to
variables.spinner1 = (NDSpinner ) findViewById(R.id.spinner1);
Also I changed the xml layout where the spinner is located
<Spinner
android:id="#+id/spinner1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/place" />
to
<com.yourpackagename.NDSpinner
android:id="#+id/spinner1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/place" />
Spinner extension class:
package com.yourpackagename;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Spinner;
import android.widget.Toast;
import java.lang.reflect.Field;
/** Spinner extension that calls onItemSelected even when the selection is the same as its previous value.
* ie This is extended "Customized class of Spinner" to get the "onItemSelected" event even if the item in the
* Spinner is already selected by the user*/
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); }
private void ignoreOldSelectionByReflection() {
try {
Class<?> c = this.getClass().getSuperclass().getSuperclass().getSuperclass();
Field reqField = c.getDeclaredField("mOldSelectedPosition");
reqField.setAccessible(true);
reqField.setInt(this, -1);
} catch (Exception e) {
Log.d("Exception Private", "ex", e);
// TODO: handle exception
}
}
#Override
public void setSelection(int position, boolean animate)
{
boolean sameSelected = position == getSelectedItemPosition();
ignoreOldSelectionByReflection();
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) {
ignoreOldSelectionByReflection();
super.setSelection(position);
}
}
The default Spinner doesn't trigger any event when you select the same item as your currently selected item. You will need to make a custom Spinner in order to do this. See How can I get an event in Android Spinner when the current selected item is selected again?
Add the same adapter every time if you want to do it using default spinner :
#Override
public void onItemSelected(AdapterView adapter, View v, int position, long lng) {
spinner.setAdapter(adapter);
}

passing Intent from class which extends ListView

This is my class which is population items in a list view , things goins well , onItemClick method is executed when clicked on a listitem , but when i pass an intent from there , it is not passing the intent , plz help me out.
public class VideosListView extends ListView implements android.widget.AdapterView.OnItemClickListener {
private List<Video> videos;
private VideoClickListener videoClickListener;
MainActivity ma;
private Context mcontext;
public VideosListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VideosListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VideosListView(Context context) {
super(context);
mcontext=context;
}
public void setVideos(List<Video> videos){
this.videos = videos;
VideosAdapter adapter = new VideosAdapter(getContext(), videos);
setAdapter(adapter);
// When the videos are set we also set an item click listener to the list
// this will callback to our custom list whenever an item it pressed
// it will tell us what position in the list is pressed
setOnItemClickListener(this);
}
// Calling this method sets a listener to the list
// Whatever class is passed in will be notified when the list is pressed
// (The class that is passed in just has to 'implement VideoClickListener'
// meaning is has the methods available we want to call)
public void setOnVideoClickListener(VideoClickListener l) {
videoClickListener = l;
}
#Override
public void setAdapter(ListAdapter adapter)
{
super.setAdapter(adapter);
}
// When we receive a notification that a list item was pressed
// we check to see if a video listener has been set
// if it has we can then tell the listener 'hey a video has just been clicked' also passing the video
#Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long id)
{
Intent intent = new Intent(mcontext,AnVideoView.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mcontext.startActivity(intent);
Log.i("VideoListView", "I am Clicked");
if(videoClickListener != null)
{
videoClickListener.onVideoClicked(videos.get(position));
Log.d("My Position ","position is" + position);
}
this is my classsnow from onItemClick() i want to pass to a activity class , how to do that thanx helping
Try This...
It will help you.
Activity activity;
public VideosListView(Activity activity) {
super(context);
this.activity=activity;
}
#Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long id)
{
Intent intent = new Intent(activity,AnVideoView.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
Log.i("VideoListView", "I am Clicked");
if(videoClickListener != null)
{
videoClickListener.onVideoClicked(videos.get(position));
Log.d("My Position ","position is" + position);
}
in onItemClick((AdapterView adapter, View v, int position, long id)
i have created a contructor to the class extending BaseAdapter , and then from the there we can easily pass our intent... it is working for me , anyways thaks helping me a lot guys
we can pass the position in the contructor , and do whatever we want to do with the item selected.

How to keep onItemSelected from firing off on a newly instantiated Spinner?

I've thought of some less than elegant ways to solve this, but I know I must be missing something.
My onItemSelected fires off immediately without any interaction with the user, and this is undesired behavior. I wish for the UI to wait until the user selects something before it does anything.
I even tried setting up the listener in the onResume(), hoping that would help, but it doesn't.
How can I stop this from firing off before the user can touch the control?
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.
}
}
}
The use of Runnables is completely incorrect.
Use setSelection(position, false); in the initial selection before setOnItemSelectedListener(listener)
This way you set your selection with no animation which causes the on item selected listener to be called. But the listener is null so nothing is run. Then your listener is assigned.
So follow this exact sequence:
Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
Referring to the answer of Dan Dyer, try to register the OnSelectListener in a post(Runnable) method:
spinner.post(new Runnable() {
public void run() {
spinner.setOnItemSelectedListener(listener);
}
});
By doing that for me the wished behavior finally occurred.
In this case it also means that the listener only fires on a changed item.
I would have expected your solution to work -- I though the selection event would not fire if you set the adapter before setting up the listener.
That being said, a simple boolean flag would allow you to detect the rogue first selection event and ignore it.
I created a small utility method for changing Spinner selection without notifying the user:
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);
}
});
}
});
}
It disables the listener, changes the selection, and re-enables the listener after that.
The trick is that calls are asynchronous to the UI thread, so you have to do it in consecutive handler posts.
Unfortunately it seems that the two most commonly suggested solutions to this issue, namely counting callback occurrences and posting a Runnable to set the callback at a later time can both fail when for example accessibility options are enabled. Here's a helper class that works around these issues. Further explenation is in the comment block.
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(); }
}
I have had LOTS of issues with the spinner firing of when I didn't want to, and all the answers here are unreliable. They work - but only sometimes. You will eventually run into scenarios where they will fail and introduce bugs into your code.
What worked for me was to store the last selected index in a variable and evaluate it in the listener. If it is the same as the new selected index do nothing and return, else continue with the listener. Do this:
//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;
//then evaluate it in your listener
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(mLastSpinnerPosition == i){
return; //do nothing
}
mLastSpinnerPosition = i;
//do the rest of your code now
}
Trust me when I say this, this is by far the most reliable solution. A hack, but it works!
Just to flesh out hints at using the onTouchListener to distinguish between automatic calls to the setOnItemSelectedListener (which are part of Activity initialization, etc.) vs. calls to it triggered by actual user interaction, I did the following after trying some other suggestions here and found that it worked well with the fewest lines of code.
Just set an Boolean field for your Activity/Fragment like:
private Boolean spinnerTouched = false;
Then just before you set your spinner's setOnItemSelectedListener, set an onTouchListener:
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
spinnerTouched = true;
return false;
}
});
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
...
if (spinnerTouched){
//Do the stuff you only want triggered by real user interaction.
}
spinnerTouched = false;
I was in similar situation, and I have a simple solution working for me.
It seems like methods setSelection(int position) and setSelected(int position, boolean animate) have different internal implementation.
When you use the second method setSelected(int position, boolean animate) with false animate flag, you get the selection without firing onItemSelected listener.
spinner.setSelection(Adapter.NO_SELECTION, false);
This will happen if you are making selection in code as;
mSpinner.setSelection(0);
Instead of above statement use
mSpinner.setSelection(0,false);//just simply do not animate it.
Edit: This method doesn't work for Mi Android Version Mi UI.
After pulling my hair out for a long time now I've created my own Spinner class. I've added a method to it which disconnects and connects the listener appropriately.
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);
}
}
}
Use it in your XML like this:
<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" />
All you have to do is retrieve the instance of SaneSpinner after inflation and call set selection like this:
mMySaneSpinner.setSelection(1, true, true);
With this, no event is fired and user interaction is not interrupted. This reduced my code complexity a lot. This should be included in stock Android since it really is a PITA.
No unwanted events from the layout phase if you defer adding the listener till the layout is finished:
spinner.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Ensure you call it only once works for JELLY_BEAN and later
spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// add the listener
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// check if pos has changed
// then do your work
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
}
});
I got a very simple answer , 100% sure it works:
boolean Touched=false; // this a a global variable
public void changetouchvalue()
{
Touched=true;
}
// this code is written just before onItemSelectedListener
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
changetouchvalue();
return false;
}
});
//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code
if(Touched)
{
// the code u want to do in touch event
}
I've found much more elegant solution to this. It involves counting how many times the ArrayAdapter (in your case "adapter")has been invoked. Let's say you have 1 spinner and you call:
int iCountAdapterCalls = 0;
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);
Declare an int counter after the onCreate and then inside onItemSelected() method put an "if" condition to check how many times the atapter has been called. In your case you have it called just once so:
if(iCountAdapterCalls < 1)
{
iCountAdapterCalls++;
//This section executes in onCreate, during the initialization
}
else
{
//This section corresponds to user clicks, after the initialization
}
Since nothing worked for me, and I have more than 1 spinner in my view (and IMHO holding a bool map is an overkill) I use the tag to count the clicks :
spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Integer selections = (Integer) parent.getTag();
if (selections > 0) {
// real selection
}
parent.setTag(++selections); // (or even just '1')
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
My small contribution is a variation on some of the above that has suited me a few times.
Declare an integer variable as a default value (or last used value saved in preferences).
Use spinner.setSelection(myDefault) to set that value before the listener is registered.
In the onItemSelected check whether the new spinner value equals the value you assigned before running any further code.
This has the added advantage of not running code if the user selects the same value again.
After having had the same problem, I came to this solutions using tags.
The idea behind it is simple: Whenever the spinner is changed programatically, make sure the tag reflects the selected position. In the listener then you check if the selected position equals the tag. If it does, the spinner selection was changed programatically.
Below is my new "spinner proxy" class:
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.
}
You will also need an XML file with the tag setup in your Values directory.
I named my file spinner_tag.xml, but that's up to you.
It looks like this:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="spinner_pos" type="id" />
</resources>
Now replace
Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);
in your code with
SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));
And make your handler somewhat look like this:
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) {
}
});
The function isUiTriggered() will return true if and only if the spinner has been changed by the user. Note that this function has a side effect - it will set the tag, so a second call in the same listener call will always return false.
This wrapper will also handle the problem with the listener being called during layout creation.
Have fun,
Jens.
Lots of answers already, here's mine.
I extend AppCompatSpinner and add a method pgmSetSelection(int pos) that allows programmatic selection setting without triggering a selection callback. I've coded this with RxJava so that the selection events are delivered via an Observable.
package com.controlj.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import io.reactivex.Observable;
/**
* Created by clyde on 22/11/17.
*/
public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
private int lastSelection = INVALID_POSITION;
public void pgmSetSelection(int i) {
lastSelection = i;
setSelection(i);
}
/**
* Observe item selections within this spinner. Events will not be delivered if they were triggered
* by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
*
* #return an Observable delivering selection events
*/
public Observable<Integer> observeSelections() {
return Observable.create(emitter -> {
setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(i != lastSelection) {
lastSelection = i;
emitter.onNext(i);
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
onItemSelected(adapterView, null, INVALID_POSITION, 0);
}
});
});
}
public FilteredSpinner(Context context) {
super(context);
}
public FilteredSpinner(Context context, int mode) {
super(context, mode);
}
public FilteredSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
super(context, attrs, defStyleAttr, mode);
}
}
An example of its usage, called in onCreateView() in a Fragment for example:
mySpinner = view.findViewById(R.id.history);
mySpinner.observeSelections()
.subscribe(this::setSelection);
where setSelection() is a method in the enclosing view that looks like this, and which is called both from user selection events via the Observable and also elsewhere programmatically, so the logic for handling selections is common to both selection methods.
private void setSelection(int position) {
if(adapter.isEmpty())
position = INVALID_POSITION;
else if(position >= adapter.getCount())
position = adapter.getCount() - 1;
MyData result = null;
mySpinner.pgmSetSelection(position);
if(position != INVALID_POSITION) {
result = adapter.getItem(position);
}
display(result); // show the selected item somewhere
}
I would try to call
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
after you call setAdapter(). Also try out calling before the adapter.
You always have the solution to go with subclassing, where you can wrap a boolean flag to your overriden setAdapter method to skip the event.
The solution with a boolean flag or a counter didn't help me, 'cause during orientation change onItemSelected() calls "overflew" the flag or the counter.
I subclassed android.widget.Spinner and made tiny additions. The relevant parts are below. This solution worked for me.
private void setHandleOnItemSelected()
{
final StackTraceElement [] elements = Thread.currentThread().getStackTrace();
for (int index = 1; index < elements.length; index++)
{
handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$
if (handleOnItemSelected)
{
break;
}
}
}
#Override
public void setSelection(int position, boolean animate)
{
super.setSelection(position, animate);
setHandleOnItemSelected();
}
#Override
public void setSelection(int position)
{
super.setSelection(position);
setHandleOnItemSelected();
}
public boolean shouldHandleOnItemSelected()
{
return handleOnItemSelected;
}
This is not an elegant solution either. In fact it's rather Rube-Goldberg but it seems to work. I make sure the spinner has been used at least once by extending the array adapter and overriding its getDropDownView. In the new getDropDownView method I have a boolean flag that is set to show the dropdown menu has been used at least once. I ignore calls to the listener until the flag is set.
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);
overriden array adapter:
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);
}
}
modified listener:
#Override
public boolean onNavigationItemSelected(
int itemPosition, long itemId) {
if (viewed) {
...
}
return false;
}
if you need to recreate activity on the fly eg: changing themes , a simple flag/counter wont work
use onUserInteraction() function to detect user activity,
reference : https://stackoverflow.com/a/25070696/4772917
I have done with simplest way:
private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;
onCreate();
spinner = (Spinner) findViewById(R.id.spinner);
listener = new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
Log.i("H - Spinner selected position", position);
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
};
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
spinner.setOnItemSelectedListener(listener);
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
Done
if () {
spinner.setSelection(0);// No reaction to create spinner !!!
} else {
spinner.setSelection(intPosition);
}
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
// real selection
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
That's my final and easy to use solution :
public class ManualSelectedSpinner extends Spinner {
//get a reference for the internal listener
private OnItemSelectedListener mListener;
public ManualSelectedSpinner(Context context) {
super(context);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setOnItemSelectedListener(#Nullable OnItemSelectedListener listener) {
mListener = listener;
super.setOnItemSelectedListener(listener);
}
public void setSelectionWithoutInformListener(int position){
super.setOnItemSelectedListener(null);
super.setSelection(position);
super.setOnItemSelectedListener(mListener);
}
public void setSelectionWithoutInformListener(int position, boolean animate){
super.setOnItemSelectedListener(null);
super.setSelection(position, animate);
super.setOnItemSelectedListener(mListener);
}
}
Use the default setSelection(...) for default behaviour or use setSelectionWithoutInformListener(...) for selecting an item in the spinner without triggering OnItemSelectedListener callback.
I need to use mSpinner in ViewHolder, so the flag mOldPosition is set in the anonymous inner class.
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int mOldPosition = mSpinner.getSelectedItemPosition();
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
if (mOldPosition != position) {
mOldPosition = position;
//Do something
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
//Do something
}
});
I would store the initial index during creation of the onClickListener object.
int thisInitialIndex = 0;//change as needed
myspinner.setSelection(thisInitialIndex);
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int initIndex = thisInitialIndex;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (id != initIndex) { //if selectedIndex is the same as initial value
// your real onselecteditemchange event
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
My solution uses onTouchListener but doesn't restricts from its use. It creates a wrapper for onTouchListener if necessary where setup onItemSelectedListener.
public class Spinner extends android.widget.Spinner {
/* ...constructors... */
private OnTouchListener onTouchListener;
private OnItemSelectedListener onItemSelectedListener;
#Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
onItemSelectedListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
#Override
public void setOnTouchListener(OnTouchListener listener) {
onTouchListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
return onItemSelectedListener != null ? new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
}
} : onTouchListener;
}
}
I might be answering too late over the post, however I managed to achieve this using Android Data binding library Android Databinding . I created a custom binding to make sure listener is not called until selected item is changed so even if user is selecting same position over and over again event is not fired.
Layout xml file
<layout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Spinner
android:id="#+id/spinner"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:spinnerMode="dropdown"
android:layout_below="#id/member_img"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:background="#drawable/member_btn"
android:padding="#dimen/activity_horizontal_margin"
android:layout_marginStart="#dimen/activity_horizontal_margin"
android:textColor="#color/colorAccent"
app:position="#{0}"
/>
</RelativeLayout>
</layout>
app:position is where you are passing position to be selected.
Custom binding
#BindingAdapter(value={ "position"}, requireAll=false)
public static void setSpinnerAdapter(Spinner spinner, int selected)
{
final int [] selectedposition= new int[1];
selectedposition[0]=selected;
// custom adapter or you can set default adapter
CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
spinner.setAdapter(customSpinnerAdapter);
spinner.setSelection(selected,false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String item = parent.getItemAtPosition(position).toString();
if( position!=selectedposition[0]) {
selectedposition[0]=position;
// do your stuff here
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
You can read more about custom data binding here Android Custom Setter
NOTE
Don't forget to enable databinding in your Gradle file
android {
....
dataBinding {
enabled = true
}
}
Include your layout files in <layout> tags
mYear.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View arg1, int item, long arg3) {
if (mYearSpinnerAdapter.isEnabled(item)) {
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});

Categories

Resources