Thursday, February 14, 2013

Customizing Spinner appearance

If you want to change appearance of Button or TextView, it is very easy. You open layout subfolder in res folder, then you open activity_main.xml and add the following lines:

android:gravity="center"
android:background="#000000"
android:textColor="#ff0000"
android:textSize="32sp"


Something like that, Button text is centered anyway and TextView background is see through by default, so not all of them may be required. But if you want to change appearance of Spinner things are becoming complicated. Naturally first solution is do Google search and see how others are doing it. That gets you to Stack Overflow and there is plenty of solutions which are based on enthusiasm of contributor to say something. That eloquent guessing, without any understanding what is actually going on, goes that far that they are claiming that you must subclass ArrayAdapter to change text color. To be honest Android documentation is not very helpful here and according to modern scientific approach people go here agile, they do trial and error in plain English. If you do not have knowledge demonstrate effort. Naturally there is much better way. Android is open source project and getting Java code for it is not difficult, in Android SDK Manager one just needs to select Sources for Android SDK and download it. Now if we take a look at typical Spinner usage we see something like this:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Spinner spinner=(Spinner)findViewById(R.id.spinner1);
    spinner.setOnItemSelectedListener(this);
    adapter=new ArrayAdapter(this, android.R.layout.simple_spinner_item, items);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
}


Appearance of Spinner is defined by android.R.layout.simple_spinner_item in collapsed state and android.R.layout.simple_spinner_dropdown_item in expanded state. If we want Spinner to look differently, we need to change layout. Unfortunately android.R.layout.simple_spinner_item is not part of Sources for Android SDK but android.widget.ArrayAdapter is available. We open it and see relevant methods:

public View getView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(position, convertView, parent, mResource);
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(position, convertView, parent, mDropDownResource);
}


Then we look for createViewFromResource, where real work is done:

private View createViewFromResource(int position, View convertView, ViewGroup parent,
        int resource) {
    View view;
    TextView text;

    if (convertView == null) {
        view = mInflater.inflate(resource, parent, false);
    } else {
        view = convertView;
    }
    try {
        if (mFieldId == 0) {
            //  If no custom field is assigned, assume the whole resource is a TextView
            text = (TextView) view;
        } else {
            //  Otherwise, find the TextView field within the layout
            text = (TextView) view.findViewById(mFieldId);
        }
    } catch (ClassCastException e) {
        Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
        throw new IllegalStateException(
                "ArrayAdapter requires the resource ID to be a TextView", e);
    }
    T item = getItem(position);
    if (item instanceof CharSequence) {
        text.setText((CharSequence)item);
    } else {
        text.setText(item.toString());
    }
    return view;
}

And there we find required information, that printed with red font. We need to supply layout which is TextView or layout containing TextView and to supply ID of TextView fot the second case. Now when we got idea what we are doing changing appearance is trivial.
Collapsed appearance layout:


Expanded appearance layout:


We save those two as custom Spinner layouts. We know that ID can be omitted and we happily omit it, less typing. We use our custom layouts like this:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Spinner spinner=(Spinner)findViewById(R.id.spinner1);
    spinner.setOnItemSelectedListener(this);
    adapter=new ArrayAdapter
(this, R.layout.col, items);
    adapter.setDropDownViewResource(R.layout.exp);
    spinner.setAdapter(adapter);
}


Obviously I saved them as col.xml and exp.xml.

1 comment: