Krøllebølle
Krøllebølle

Reputation: 3028

Dynamic Spinner with icons inheriting AppCompat theme

I am trying to achieve two things:

  1. Make my Spinners inherit from the AppCompat themes.
  2. Add icons to the spinner elements, as is possible in the Toolbar popup menus.

Since I am not able to achieve the first point I am focusing on that, but I also want to add icons later on. As it is now, my Toolbar popup menus inherit the AppCompat theme, but the Spinners do not, as shown in the pictures below. The first image shows the (proper) popup menu from the Toolbar, while the second shows the popup menu from a Spinner. This is an example of a Spinner that is not inheriting the style. Or should this popup menu style be expected?

Toolbar inherits from AppCompat style Spinners do not inherit from AppCompat style

I have tried loads of things, so there are likely multiple duplicates of this question, but I am not able to make it work. What can be wrong in the code below? After the theme is inherited correctly, how can I add icons later on? Minimum SDK version is 16, target is 23.

themes.xml:

<resources>

    <style name="MyTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>

        <item name="colorPrimary">@color/my_brown</item>
        <item name="colorPrimaryDark">@color/my_dark_gray</item>
        <item name="colorAccent">@color/my_green</item>

        <!-- This is just a test, it makes no difference. -->
        <item name="android:spinnerStyle">@style/MySpinnerStyle</item>

    </style>

    <style name="MyTheme" parent="MyTheme.Base"></style>

    <!--
        ActionBar style, applied directly to XML elements
    -->
    <style name="MyActionBarStyle" parent="@style/Widget.AppCompat.ActionBar">
        <item name="theme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
        <item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:background">@color/my_brown</item>
        <item name="android:minHeight">?attr/actionBarSize</item>
    </style>

    <!--
        Spinner style, for testing. Also tried applied directly to xml Spinners.
    -->
    <style name="MySpinnerStyle" parent="@style/Widget.AppCompat.Spinner">
        <item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
        <item name="android:popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
     </style>
</resources>

The Spinner is set up as simply as:

ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, mCategories);
mSpinner.setAdapter(adapter);

where mSpinner is the inflated Spinner and mCategories is a String array. In XML the Spinner is defined as

<Spinner
    android:id="@+id/my_spinner"
    android:layout_weight="1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"/>

I have tried adding various styles directly to the Spinner, but it does not work.

In my AndroidManifest.xml I have added the following to the Application tag:

android:theme="@style/MyTheme"

Upvotes: 2

Views: 2293

Answers (2)

Kr&#248;lleb&#248;lle
Kr&#248;lleb&#248;lle

Reputation: 3028

The cause of my problem was in the line

ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, mCategories);

Here I am using android.R.layout.simple_spinner_item which is probably meant for the Spinner itself. Using android.R.layout.simple_spinner_dropdown_item instead gives the desired look for the dropdown items.

The difference between those are described in some detail in this question, though the images are for older Android versions.

For reference, here are the two layouts taken directly from the Android source code.

simple_spinner_item

<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:textAlignment="inherit"/>

simple_spinner_dropdown_item

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerDropDownItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/dropdownListPreferredItemHeight"
    android:ellipsize="marquee"/>

Adding icons to the dropdown items

Now, for the icon part, I followed the steps from @euitam and ended up with the following:

MyAdapter:

public class MyAdapter extends ArrayAdapter<String>
{
    private String[] mCategories;
    private int[] mIcons;

    public CategoryDropDownAdapter(Context context, int layoutResourceId, String[] categories)
    {
        super(context, layoutResourceId, categories);
        mCategories = categories;

        // Add the same icon to all items, just for testing.
        mIcons = new int[mCategories.length];
        for (int i = 0; i < mIcons.length; i++)
        {
            mIcons[i] = R.drawable.my_icon;
        }
    }

    /**
    * View for a dropdown item.
    * */
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent)
    {
        View rowView = convertView;

        if (rowView == null)
        {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            rowView = inflater.inflate(R.layout.my_spinner_categories_dropdown_item, parent, false);
        }

        TextView categoryText = (TextView) rowView.findViewById(R.id.my_spinner_dropdown_item_text);
        categoryText.setText(mCategories[position]);

        ImageView icon = (ImageView) rowView.findViewById(R.id.my_spinner_dropdown_item_icon);
        icon.setImageResource(mIcons[position]);

        return rowView;
    }

    /**
     * The Spinner View that is selected and shown in the *Spinner*, i.e. not the dropdown item.
     * */
    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        View spinnerView = convertView;

        if (spinnerView == null)
        {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            spinnerView = inflater.inflate(R.layout.my_spinner_categories_spinner_item, parent, false);
        }

        TextView categoryText = (TextView) spinnerView.findViewById(R.id.my_spinner_item_text);
        categoryText.setText(mCategories[position]);

        return spinnerView;
    }

}

my_spinner_categories_dropdown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/my_spinner_dropdown_item_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <!-- Stolen from android.R.layout.simple_spinner_dropdown_item -->
    <TextView
        android:id="@+id/my_spinner_dropdown_item_text"
        style="?android:attr/spinnerDropDownItemStyle"
        android:singleLine="true"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:ellipsize="marquee" />

</LinearLayout>

my_spinner_categories_spinner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <!-- Stolen from android.R.layout.simple_spinner_dropdown_item -->
    <TextView
        android:id="@+id/my_spinner_item_text"
        style="?android:attr/spinnerDropDownItemStyle"
        android:singleLine="true"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:ellipsize="marquee" />

</LinearLayout>

And finally, setting the adapter:

MyAdapter adapter = new MyAdapter(getContext(), android.R.layout.simple_spinner_dropdown_item, mCategories);
mSpinner.setAdapter(adapter);

Upvotes: 0

euitam
euitam

Reputation: 172

I managed to do it (or to be very close to it) by changing themes and styles like that:

Just add to your theme:

<item name="android:spinnerDropDownItemStyle">@style/MySpinnerItem</item>

Then create a style for MySpinnerItem that inherits from Widget.AppCompat.DropDownItem.Spinner:

<style name="MySpinnerItem" parent="@style/Widget.AppCompat.DropDownItem.Spinner">
    <item name="android:textColor">@color/your_text_color</item>
    <item name="android:textSize">16sp</item>
    <item name="android:paddingLeft">16dp</item>
    <item name="android:paddingStart" tools:targetApi="jelly_bean_mr1">16dp</item>
    <item name="android:paddingRight">16dp</item>
    <item name="android:paddingEnd" tools:targetApi="jelly_bean_mr1">16dp</item>
</style>

That's it for the AppCompat theme.

Finally, if you want to add icons to list items, you have to create a custom layout and set it programmatically. You can follow this tutorial http://android-er.blogspot.sg/2010/12/custom-arrayadapter-for-spinner-with.html that explains it. Basically you have to:

  • create a custom layout
  • create a custom adapter that inherits from ArrayAdapter
  • implements getCutomView() methods to set your different images for each item
  • finally set your adapter to the spinner with MyCustomAdapter.createFromResource(this, R.array.my_data, R.layout.my_cutom_item_layout);

Upvotes: 1

Related Questions