Spinner with empty default selected item
Asked Answered
K

5

10

I'm trying to create a spinner with default empty selected item, but it displays the first item from the choices of spinner. If I add null value to my string, which is the source of choices in spinner, then after opening spinner that empty row is displayed. How should I do it? Here's code I'm using:

  String[] ch = {"Session1", "Session2", "Session3"};
  Spinner sp = (Spinner)findViewById(R.id.spinner1);
  TextView sess_name = findViewById(R.id.sessname);
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item,ch);
  sp.setAdapter(adapter);

  adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

  sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener({
      @Override
      public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
          int index = arg0.getSelectedItemPosition();
          sess_name.setText(ch[index]);

          Toast.makeText(getBaseContext(), "You have selected item : " + ch[index], Toast.LENGTH_SHORT).show();
      }
Korry answered 14/7, 2012 at 12:49 Comment(1)
Are you concerned that the blank line is allowed as a selection (simple to fix) or that it is showing at all (not sure how to fix)?Selfregulating
T
15

Barak's solution have a problem. When you select the first item, Spinner won't call OnItemSelectedListener's onItemSelected() and refresh the empty content because the previous position and selection position both is 0.

First put a empty string at the begin of your string array:

String[] test = {" ", "one", "two", "three"};

Second build adapter, don't modify getView(), modify getDropDownView(). Set the empty View's height to 1px.

public class MyArrayAdapter extends ArrayAdapter<String> {

    private static final int ITEM_HEIGHT = ViewGroup.LayoutParams.WRAP_CONTENT;

    private int textViewResourceId;


    public MyArrayAdapter(Context context,
                          int textViewResourceId,
                          String[] objects) {
        super(context, textViewResourceId, objects);
        this.textViewResourceId = textViewResourceId;
    }

    @Override
    public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
        TextView textView;

        if (convertView == null) {
            textView = (TextView) LayoutInflater.from(getContext())
                   .inflate(textViewResourceId, parent, false);
        } else {
            textView = (TextView) convertView;
        }

        textView.setText(getItem(position));
        if (position == 0) {
            ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
            layoutParams.height = 1;
            textView.setLayoutParams(layoutParams);
        } else {
            ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
            layoutParams.height = ITEM_HEIGHT;
            textView.setLayoutParams(layoutParams);
        }

        return textView;
    }
}
Trust answered 15/3, 2013 at 6:6 Comment(2)
+1, thanks, but I still see something or some line (maybe the "1px effect")Imf
works well, what I ended up using. I would not hard code 96 though, use LayoutParams.WRAP_CONTENT insteadBid
A
6

I'm a little late to the party, but here is what I did to solve this.
If the user cancels out of selecting an initial item the spinner will retain the initial empty state. Once an initial item has been selected it works as 'normal'
Works on 2.3.3+, I have not tested on 2.2 and below

First, create an adapter class...

public class EmptyFirstItemAdapter extends ArrayAdapter<String>{
    //Track the removal of the empty item
    private boolean emptyRemoved = false;

    /** Adjust the constructor(s) to fit your purposes. */
    public EmptyFirstitemAdapter(Context context, List<String> objects) {
        super(context, android.R.layout.simple_spinner_item, objects);
        setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    }

    @Override
    public int getCount() {
        //Adjust the count based on the removal of the empty item
        if(emptyRemoved){
            return super.getCount();            
        }
        return super.getCount()-1;            
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        if(!emptyRemoved){
            // Remove the empty item the first time the dropdown is displayed.
            emptyRemoved = true;
            // Set to false to prevent auto-selecting the first item after removal.
            setNotifyOnChange(false);
            remove(getItem(0));
            // Set it back to true for future changes.
            setNotifyOnChange(true);
        }
        return super.getDropDownView(position, convertView, parent);
    }

    @Override
    public long getItemId(int position) {
        // Adjust the id after removal to keep the id's the same as pre-removal.
        if(emptyRemoved){
            return position +1;
        }
        return position;
    }

}

Here is the string array I used in strings.xml

<string-array name="my_items">
    <item></item>
    <item>Item 1</item>
    <item>Item 2</item>
</string-array>

Next, add an OnItemSelectedListener to your Spinner...

mSpinner = (Spinner) mRootView.findViewById(R.id.spinner);
String[] opts = getResources().getStringArray(R.array.my_items);
//DO NOT set the entries in XML OR use an array directly, the adapter will get an immutable List.
List<String> vals = new ArrayList<String>(Arrays.asList(opts));
final EmptyFirstitemAdapter adapter = new EmptyFirstitemAdapter(getActivity(), vals);
mSpinner.setAdapter(adapter);
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    //Track that we have updated after removing the empty item
    private boolean mInitialized = false;
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if(!mInitialized && position == 0 && id == 1){
            // User selected the 1st item after the 'empty' item was initially removed,
            // update the data set to compensate for the removed item.
            mInitialized = true;
            adapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // Nothing to do
    }
});

It may not be a 'perfect' solution, but I hope it helps someone.

Atul answered 30/8, 2013 at 16:42 Comment(5)
when I select the first item, it does not update. It only updates if I select another item and then the first itemGainsborough
Did you implement the getItemId() method on the adapter as noted in my example?Atul
Yes I copied all of your code sample, it surely has to do with the +1 offset because it only behaves this way if I click on the 1 item the first time.Gainsborough
What version of android are you running on? Are you running on an emulator or a physical device? I just built a sample app with an activity that only has this spinner. I ran it on 2.3.3, 4.0.3 and 4.1.2 emulators as well as on a Nexus 4 (4.2.2) with no issues.Atul
Then I must be doing something wrong, but what. I tested on a Galaxy Nexus deviceGainsborough
S
2

After some thinking, I believe I've come up with a method to achieve your goal. It involves creating a custom adapter and setting/maintaining a flag to determine if an item from the spinner has been selected. Using this method you do not need to create/use false data (your empty string).

Basically, the adapters getView method sets the text for the closed spinner. So if you override that and set a conditional in there, you can have a blank field on startup and after you make a selection have it appear in the closed spinner box. The only thing is you need to remember to set the flag whenever you need to see the value in the closed spinner.

I've created a small example program (code below).

Note that I only added the single constructor I needed for my example. You can implement all the standard ArrayAdapter constructors or only the one(s) you need.

SpinnerTest.java

public class SpinnerTestActivity extends Activity {
    private String[] planets = { "Mercury", "Venus", "Earth", "Mars",
            "Jupiter", "Saturn", "Uranus", "Neptune" };
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        CustomAdapter adapter = new CustomAdapter(this,              // Use our custom adapter
                android.R.layout.simple_spinner_item, planets);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                    int pos, long id) {
                CustomAdapter.flag = true;                       // Set adapter flag that something
                has been chosen
            }
        });
    }
}

CustomAdapter.java

public class CustomAdapter extends ArrayAdapter {
    private Context context;
    private int textViewResourceId;
    private String[] objects;
    public static boolean flag = false;
    public CustomAdapter(Context context, int textViewResourceId,
            String[] objects) {
        super(context, textViewResourceId, objects);
        this.context = context;
        this.textViewResourceId = textViewResourceId;
        this.objects = objects;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null)
            convertView = View.inflate(context, textViewResourceId, null);
        if (flag != false) {
            TextView tv = (TextView) convertView;
            tv.setText(objects[position]);
        }
        return convertView;
    }
}
Selfregulating answered 14/7, 2012 at 13:47 Comment(2)
yes i want to remove the blank line from dropdown .how can i do that??Korry
Updated my answer with a solution.Selfregulating
B
0

Here is what I use. It properly handles null (empty) selection in a generic manner. It works with any model class T, as long as class T properly implements toString(), to display the text shown in the spinner, and equals(), so that items may be selected by reference rather than by positional index.

package com.10xdev.android.components;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * A spinner where no selection is possible, and other enhancements.
 * requires model class to properly implement Object.equals, with semantic comparaison (such as id comparaison)
 * and a proper toString(), whose result will be displayed in the spinner
 *
 * @author tony.benbrahim
 */

public class EnhancedSpinner<T> extends Spinner {
    private final EnhanceArraySpinnerAdapter<T> spinnerAdapter;
    private final List<T> items = new ArrayList<>();
    private T selected = null;

    public EnhancedSpinner(final Context context, final AttributeSet attributeSet) {
        super(context, attributeSet);
        spinnerAdapter = new EnhanceArraySpinnerAdapter<>(context, items);
        setAdapter(spinnerAdapter);
    }

    /**
     * sets the items to be displayed
     *
     * @param items
     */
    public void setItems(final List<T> items) {
        this.items.clear();
        //very iffy, but works because of type erasure
        this.items.add((T) "");
        this.items.addAll(items);
        spinnerAdapter.notifyDataSetChanged();
        updateSelected();
    }

    /**
     * set the selected item. this may be called before or after setting items
     *
     * @param item the item to select, or null to clear the selection
     */
    public void setSelected(final T item) {
        this.selected = item;
        updateSelected();
    }

    /**
     * gets the selected item, or null if no item is selected
     *
     * @return
     */
    @Override
    public T getSelectedItem() {
        return getSelectedItemPosition() != 0 ? (T) super.getSelectedItem() : null;
    }

    /**
     * set the error message for the select
     *
     * @param errorMessage
     */
    public void setError(final String errorMessage) {
        final TextView errorText = (TextView) getSelectedView();
        errorText.setError("error");
        errorText.setTextColor(Color.RED);
        errorText.setText(errorMessage);
    }

    private void updateSelected() {
        if (selected == null) {
            setSelection(0);
        } else {
            for (int i = 1; i < items.size(); ++i) {
                if (selected.equals(items.get(i))) {
                    setSelection(i);
                    break;
                }
            }
        }
    }

    private class EnhanceArraySpinnerAdapter<T> extends ArrayAdapter<T> {
        private final LayoutInflater inflater;

        public EnhanceArraySpinnerAdapter(final Context context, final List<T> objects) {
            super(context, android.R.layout.simple_spinner_item, objects);
            inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public View getDropDownView(final int position, final View convertView, final ViewGroup parent) {
            final TextView textView = convertView != null ? (TextView) convertView
                    : (TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
            final Object item = getItem(position);
            textView.setText(item.toString());
            final ViewGroup.LayoutParams layoutParams = textView.getLayoutParams();
            layoutParams.height = position == 0 ? 1 : LayoutParams.WRAP_CONTENT;
            textView.setLayoutParams(layoutParams);
            return textView;
        }
    }
}
Bid answered 7/11, 2016 at 20:30 Comment(0)
P
-1

You have to put the first element of the spinner empty, or with an string indicating that nothing is selected like the following:

String[] ch= {"","Session1", "Session2", "Session3"};

or

String[] ch= {"Nothing selected", "Session1", "Session2", "Session3"};

hope to help

Popelka answered 14/7, 2012 at 13:9 Comment(2)
He's got that, he doesn't want it displayed in the dropdown.Selfregulating
exactly i know this stuff but i want to remove blank line from dropdown is there any way out??Korry

© 2022 - 2024 — McMap. All rights reserved.