How to hide one item in an Android Spinner
Asked Answered
T

9

108

I am looking for a way to hide one item in an Android spinner widget. This would allow you to simulate a spinner with no items selected, and ensures that the onItemSelected() callback is always called for every item selected (if the hidden item is the "current" one). Normally there is always one item in the spinner that doesn't generate a callback, namely the current one.

There is some code on stackoverflow for how to disable (gray out) items, but not how to hide items completely as if they don't exist.

After much experimentation I've come up with a somewhat hack-ish solution that works on various old and new Android platforms. It has some minor cosmetic drawbacks which are hard to notice. I'd still like to hear of a more official solution, other than "don't do that with a spinner".

This always hides the first item in the spinner, but could fairly easily be extended to hide an arbitrary item or more than one item. Add a dummy item containing an empty string at the start of your list of spinner items. You may want to set the current spinner selection to item 0 before the spinner dialog opens, this will simulate an unselected spinner.

Spinner setup example with ArrayAdapter method override:

List<String> list = new ArrayList<String>();
list.add("");   //  Initial dummy entry
list.add("string1");
list.add("string2");
list.add("string3");

// Populate the spinner using a customized ArrayAdapter that hides the first (dummy) entry
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list) {
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent)
    {
        View v = null;

        // If this is the initial dummy entry, make it hidden
        if (position == 0) {
            TextView tv = new TextView(getContext());
            tv.setHeight(0);
            tv.setVisibility(View.GONE);
            v = tv;
        }
        else {
            // Pass convertView as null to prevent reuse of special case views
            v = super.getDropDownView(position, null, parent);
        }

        // Hide scroll bar because it appears sometimes unnecessarily, this does not prevent scrolling 
        parent.setVerticalScrollBarEnabled(false);
        return v;
    }
};

dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mySpinner.setAdapter(dataAdapter);
Teri answered 25/3, 2012 at 19:44 Comment(7)
what have you found on the other interwebs? what have you tried so far?Leviticus
Sorry lah, i don't know how to do.Mccullough
Nice solution! But I think the tv.setVisibility(View.GONE); line is unnecessary. Commenting it out does not seem to make any (visual) difference, at least on Android 4.4.2/KitKit (on an LG/Google Nexus 4).Varioloid
The answer in this question works well ..Vomit
This may not be an improvement, but I used setTag(1) on the textView at position 0, then used convertView.getTag() != null to determine if the reused view was the 0 height view created for position 0 or a normal view used for other spinner items. This was so I could use super.getDropDownView(position, convertView, parent) sometimes instead of always creating a new view.Clarsach
can any one look into my question ?Baryton
it's working, nice, just what I was looking for.Dardanelles
T
55

To hide an arbitrary item or more than one item I think that you can implement your own adapter and set the index (or array list of indexes) that you want to hide.

public class CustomAdapter extends ArrayAdapter<String> {
     
     private int hidingItemIndex;
     
     public CustomAdapter(Context context, int textViewResourceId, String[] objects, int hidingItemIndex) {
         super(context, textViewResourceId, objects);
         this.hidingItemIndex = hidingItemIndex;
     }

     @Override
     public View getDropDownView(int position, View convertView, ViewGroup parent) {
         View v = null;
         if (position == hidingItemIndex) {
             TextView tv = new TextView(getContext());
             tv.setVisibility(View.GONE);
             v = tv;
         } else {
             v = super.getDropDownView(position, null, parent);
         }
         return v;
     }
 }

And use your custom adapter when you create the list of items.

List<String> list = new ArrayList<String>();
list.add("");   //  Initial dummy entry
list.add("string1");
list.add("string2");
list.add("string3");

int hidingItemIndex = 0;

CustomAdapter dataAdapter = new CustomAdapter(this, android.R.layout.simple_spinner_item, list, hidingItemIndex);

dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mySpinner.setAdapter(dataAdapter);

(I have not tested the code)

Tolidine answered 1/3, 2013 at 16:52 Comment(9)
This is not a solution. The update to the question provides the correct code.Leviticus
Without tv.setHeight(0), the TextView is still visible.Capriccioso
hi, i have used this code to hide the first item in my spinner , it is working fine , my spinner will showing me the second item , but when i clicked that second item the text on that item will be set to my spinner, i do not want show any text on my spinner when i clicked to that item , please guide me?Southwards
Amazing. Set text to TextView as "Select something" and you have a custom spinner with a prompt text. Great.Henrietta
Thanks very much. I just had one item in my array. I added it and set the height of first one to 0. Works perfectPratt
One downside of this solution is that the convertView is not being utilized.Skijoring
Not working here. If I hide several rows, the dropdown starts to work incorrectly. E.g. the popup is not opened at all, all content is shown within the component area. Hard to explain..Epanaphora
+ if height is set, the visible area of the dropdown will be ended there.Epanaphora
What did the trick for me was passing null in super.getDropDownView(position, null, parent);, when I pass the convertView I get weird behaviorEnchanting
V
24

It's easier to hide an item at the end of the list by truncating the list.

But you have to select it first so it appears in the spinner, and then check if the selection has been changed to one of the items displayed.

List<String> list = new ArrayList<String>();
list.add("string1");
list.add("string2");
list.add("string3");
list.add("[Select one]");
final int listsize = list.size() - 1;

ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, list) {
    @Override
    public int getCount() {
        return(listsize); // Truncate the list
    }
};

dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mySpinner.setAdapter(dataAdapter);
mySpinner.setSelection(listsize); // Hidden item to appear in the spinner
Voltaism answered 2/2, 2014 at 20:23 Comment(4)
I was searching for a half hour trying to find a clean approach, and this is by far the best approach. Just truncate the list, but the item actually exist. Excellent.Koloski
This doesn't seem to work in Lollipop, the [Select one] test doesn't show up initially in the Spinner. The same code on older Android versions does seem to do what we all want.Tumer
The spinner text is changed to 'String3' on orientation change even spinner is untouched. @VoltaismMcnew
can any one look into my question ?Baryton
E
7

To hide any item in the spinner dropdown you need to pass the position of item which needs to hided based on criteria required. I have achieved this in a use case of hiding the item which is selected from dropdown

public class CustomAdapter extends ArrayAdapter<String> {

private List<String> dates;
private int hideItemPostion;

public CustomAdapter (Context context, int resource, List<String> dates) {
    super(context, resource,dates);
    this.dates=dates;
}
public void setItemToHide(int itemToHide)
{
    this.hideItemPostion =itemToHide;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
    View v = null;
    if (position == hideItemPostion) {
        TextView tv = new TextView(getContext());
        tv.setVisibility(View.GONE);
        tv.setHeight(0);
        v = tv;
        v.setVisibility(View.GONE);
    }
    else
        v = super.getDropDownView(position, null, parent);
    return v;
}}

And setting the adapter is something like this

final CustomAdapter dataAdapter = new CustomAdapter(this,R.layout.spinner_item,dates);
    dataAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
    spinner.setAdapter(dataAdapter);
    dataAdapter.setItemToHide(0);

On selecting some items from the dropdown also the position needs to changed

 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, final int i, long l) {
        dataAdapter.notifyDataSetChanged();
            mEPGDateSelector.setSelection(i);
            dataAdapter.setItemToHide(i);}

             @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });
Ekaterinoslav answered 27/1, 2017 at 9:53 Comment(1)
This algorithm works... Creating new View object ( TextView ) and passing it to adapter as return, works like charm.. Moreover, you dont need to write setItemToHide function you can use if condition that matter your purposeLuciano
T
1

Just for interest, I made a solution to use "Prompt" as a hint. This code is made for Xamarin.Android, but it could be perfectly ported to Java in 10 minutes. Use it like a simple ArrayAdapter without adding 0-indexed or count-indexed items to source array. It also set SpinnerGeolocation.SelectedItemId to -1 when nothing is chosen (hint is the current item).

public class ArrayAdapterWithHint<T>: ArrayAdapter<T>
{
    protected bool HintIsSet = false;
    protected int HintResource = 0;

    public ArrayAdapterWithHint(Context context, int textViewResourceId,
                   T[] objects)
        : base(context, textViewResourceId, objects)
    {
    }
    public ArrayAdapterWithHint(Context context, int hintResource,
                   int textViewResourceId, T[] objects)
        : base(context, textViewResourceId, objects)
    {
        HintResource = hintResource;
    }
    public ArrayAdapterWithHint(Context context, int textViewResourceId,
             IList<T> objects)
        : base(context, textViewResourceId, objects)
    {
    }
    public ArrayAdapterWithHint(Context context, int hintResource,
                    int textViewResourceId, IList<T> objects)
        : base(context, textViewResourceId, objects)
    {
        HintResource = hintResource;
    }

    public override View GetDropDownView(int position, View convertView,
                ViewGroup parent)
    {
        if (HintIsSet)
            return base.GetDropDownView(position + 1,
                               convertView, parent);
        return base.GetDropDownView(position, convertView, parent);
    }

    public override View GetView(int position, View convertView,
                      ViewGroup parent)
    {
        if (!HintIsSet && parent is Spinner && 
                    !string.IsNullOrWhiteSpace((parent as Spinner).Prompt))
        {
            Insert((parent as Spinner).Prompt, 0);
            HintIsSet = true;
            (parent as Spinner).SetSelection(base.Count - 1);
        }
        if (HintIsSet && position >= base.Count - 1)
        {
            View hintView = base.GetView(0, convertView, parent);
            if (hintView is TextView)
                (hintView as TextView).SetTextAppearance(
                                                     Context, HintResource);
            return hintView;
        }
        if (HintIsSet && position < base.Count - 1)
            return base.GetView(position + 1, convertView, parent);
        return base.GetView(position, convertView, parent);
    }

    public override long GetItemId(int position)
    {
        if (HintIsSet)
        {
            if (position >= base.Count - 1)
                return -1;
            return position;
        }
        return base.GetItemId(position);
    }

    public override int Count
    {
        get
        {
            return base.Count > 0 && HintIsSet ? base.Count - 1 : base.Count;
        }
    }
}
Timbal answered 24/5, 2014 at 16:3 Comment(1)
can any one look into my question ?Baryton
S
1

I found this solution which solved my problem.

final Spinner mySpinner = (Spinner)findViewById(R.id.spinner_triptype);

   final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.spinner_item, R.id.weekofday, triptype_initial);

   final ArrayAdapter<String> adapter_temp = new ArrayAdapter<String>
(this,R.layout.spinner_item, R.id.weekofday, triptype_array);


   mySpinner.setAdapter(adapter);
    mySpinner.setOnTouchListener(new View.OnTouchListener() {
       @Override
       public boolean onTouch(View v, MotionEvent event) {
       // display your error popup here
        if(flag_spinner_isFirst){
           mySpinner.setAdapter(adapter_temp);
           flag_spinner_isFirst = false;
          }
           v.onTouchEvent(event);
           return true;

       }
    });
Stank answered 16/8, 2014 at 13:24 Comment(0)
S
0

I think it will be better to put validation on the Array List rather than on Spinner because once the item is filtered, it will be safe to add in Spinner

Satanic answered 27/2, 2013 at 12:54 Comment(0)
M
0

Another approach which worked best for me is to return a new blank view object. This is considerably a clean approach as you are not playing with array elements.

Create your adapter class extending ArrayAdapter

inside your method

public View getView(int position, View convertView, ViewGroup parent) {
    View row = getCustomView();
    if(position==0) // put the desired check here.
         {
            row  = new View(context);
         }
    }
    return row;
}
Magi answered 17/1, 2017 at 12:54 Comment(0)
N
0

This is very old question but I've found a nice (and probably) clean way to not show first items as well. Inspired by @Romich's answer I've added similar logic to skip first item(s).

This effectively hides arbitrary number of items (1 by default). The code only reports the size of the objects to render to be shorter than it actually is and also changes index of items to be rendered so we skip arbitrary number of items.

To keep things simple I've excluded solution that I'm currently using which supports hiding list of random items, but that can easily be managed by few tweaks to the code.

class ArrayAdapterCustom(context: Context, textViewResourceId: Int, vararg objects: String)
    : ArrayAdapter<String>(context, textViewResourceId, objects) {

    //Can skip first n items (skip 1 as default)
    var hideFirstItemsCount = 1

    override fun getCount(): Int {
        return super.getCount() - hideFirstItemsCount
    }

    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
        return super.getDropDownView(position + hideFirstItemsCount, convertView, parent)
    }
}
Nard answered 29/7, 2019 at 12:26 Comment(0)
E
0

A better approach with no need to adjust the model.

public class HidableSpinnerArrayAdapter<T> extends ArrayAdapter<T> {

    ...

    @Override
    public boolean isEnabled(int position) {
        // override this check what items are enabled/disabled
    }

    // Change color item
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        // when hiding items, cannot reuse views
        View view = super.getDropDownView(position, 
            null /* convertView usually */, parent);
        if (!isEnabled(position)) {
            TextView tv = (TextView) view;
            tv.setEnabled(false);
            tv.setMaxHeight(0);
            tv.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
            return tv;
        }
        else {
            return view;
        }
    }
}
Epanaphora answered 30/5, 2021 at 17:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.