Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Spinner data view not updating on item re-selected

I'm trying to customize a Spinner to be multi-selectable. I have successfully made it multi-selectable, but when an item is reselected, the view inside of the spinner does not get updated.

The Problem

When an item is reselected in the Spinner, the getView() of my SpinnerAdapter gets called, produces the View that I want it to, but that View somehow does not get displayed. I have stepped through everything in the debugger and I cannot find any differences between when a different item is selected or the same item is reselected. I have been through all of the code in Spinner AbsSpinner and AdapterView and I cannot find what may be causing this.

For Example

The spinner is populated with 6 items: Choice 1-6. The spinner originates with all items unselected. Choice 1, 2, and 4 are selected. The view inside the spinner displays "Choice 1, Choice 2, Choice 4" correctly.

Choice 4 is unselected (reselection as far as the Spinner is concerned). Choice 4 is correctly unchecked from the list, but the view inside the spinner does not update. It still displays "Choice 1, Choice 2, Choice 4". Stepping through the debugger, getView() on my SpinnerAdapter gets called and everything. For some reason, the View doesn't actually get displayed.

Choice 2 is unselected (this is not a "re-selection" to the Spinner). Here everything functions as expected. Both the list items and the view inside the Spinner are updated.

Some Code

The multi-selectable Spinner

public class MultiSelectSpinner extends Spinner {

    private OnItemSelectedListener listener;

    public MultiSelectSpinner(Context context) {

        super( context );
    }

    public MultiSelectSpinner(Context context, AttributeSet attrs) {

        super( context, attrs );
    }

    public MultiSelectSpinner(Context context, AttributeSet attrs, int defStyle) {

        super( context, attrs, defStyle );
    }

    @Override
    public void setSelection( int position ) {

        super.setSelection( position );
        if ( listener != null ) {

            listener.onItemSelected( this, getSelectedView(), position, getAdapter().getItemId( position ) );
        }
    }

    public void setOnItemSelectedEvenIfUnchangedListener( OnItemSelectedListener listener ) {

        this.listener = listener;
    }
}

Spinner Adapter

Only posting the important pieces here. Leaving out getters/setters, etc.

public class FormChoiceSpinnerAdapter implements SpinnerAdapter, OnItemSelectedListener {

    private Choice[] choices;
    private String title;
    private final DataSetObservable dataSetObservable = new DataSetObservable();

    public FormChoiceSpinnerAdapter(String[] choices, String title) {

        setChoices( new Choice[choices.length] );
        for (int i = 0; i < choices.length; i++) {

            getChoices()[i] = new Choice( choices[i] );
        }
    }

    @Override
    public View getView( int position, View convertView, ViewGroup parent ) {

        Context context = parent.getContext();
        if ( convertView == null ) {

            convertView = LayoutInflater.from( context ).inflate( android.R.layout.simple_spinner_item, parent, false );
        }

        String displayString = "";

        for (int i = 0; i < getChoices().length; i++) {

            Choice choice = getChoices()[i];

            if ( choice.isSelected() ) {

                displayString += choice.getLabel() + ", ";
            }
        }

        if ( displayString.length() > 0 ) {

            displayString = displayString.trim().substring( 0, displayString.length() - 2 );
        }
        else {

            displayString = getTitle() + "...";
        }

        ( (TextView) convertView ).setText( displayString );

        return convertView;
    }

    @Override
    public View getDropDownView( int position, View convertView, ViewGroup parent ) {

        Context context = parent.getContext();
        if ( convertView == null ) {

            convertView = LayoutInflater.from( context ).inflate( R.layout.simple_dropdown_item, parent, false );
        }

        Choice choice = getChoices()[position];
        TextView text = (TextView) convertView.findViewById( android.R.id.text1 );
        text.setText( choice.getLabel() );

        ImageView selectedImage = (ImageView) convertView.findViewById( R.id.image_selected );
        int visibility = choice.isSelected() ? View.VISIBLE : View.GONE;
        selectedImage.setVisibility( visibility );

        return convertView;
    }

    @Override
    public void onItemSelected( AdapterView<?> parent, View view, int position, long id ) {

        Choice choice = getChoices()[position];

        choice.setSelected( !choice.isSelected() );

        ImageView imageView = (ImageView) view.findViewById( R.id.image_selected );

        if ( imageView != null ) {
            imageView.setVisibility( choice.isSelected() ? View.VISIBLE : View.GONE );
        }    
    }

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

        //No op
    }

    public static class Choice {

        private boolean selected;
        private String label;

        public Choice(String label) {

            this.label = label;
            selected = false;
        }
    }
}
like image 731
James McCracken Avatar asked Jun 21 '13 20:06

James McCracken


1 Answers

I know this is very late, but I found a solution for a similar problem I was having. Try adding adapter.notifyDataSetChanged() to your onItemSelected(AdapterView<?> parent, View view, int pos, long id) Override method.

My spinner is supposed to show a default message until the user actually selects a value from it. It was working fine unless to user selected the first item in the dropdown, then the spinner wouldn't update itself. Adding adapter.notifyDataSetChanged() worked.

I also looked through some of the source code and I couldn't find exactly what was happening, but I have an idea. Since the Spinner doesn't call the OnItemSelectedListener when selecting the value that is already selected (unless you extend Spinner, which I did with the help of this SO answer), my guess is that if you try to deselect your most recently selected value, Spinner doesn't recognize that as a deselection, and therefore won't redraw the view. For you, selecting 1, 2, 4 in that order, then trying to deselect 4 wouldn't trigger the redraw response, but then deselecting 2 would cause the redraw.

I could be wrong, but I know that our problems were similar and this worked for me.

like image 99
peteross Avatar answered Oct 04 '22 22:10

peteross