Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Android Selector to change item color in PopupMenu?

Tags:

java

android

I have a button in my OptionMenu which when touched will open a popup menu of items which are fetched at run time and added programmatically (so can't use hard coded xml menu items). I want to highlight a subset of these items according to their value, I used what is suggested here: How to customize item background and item text color inside NavigationView? to try and get the items to be colored differently. However, all of the items are colored the same despite the isChecked() value being different.

Here is a small working example of the issue:
MainActivity.java

@Override
public boolean onCreateOptionsMenu(Menu menu) { 
    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.actionbar_menu, menu);
    return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    switch(id) {
          case R.id.action_bar_button:{
            showMenu();
            return true;
        }
    }
    return super.onOptionsItemSelected(item);
}

private void showMenu() {
    View v = findViewById(R.id.action_bar_button);
    Context wrapper = new ContextThemeWrapper(this, R.style.MyPopupMenu);
    PopupMenu popupMenu = new PopupMenu(wrapper, v);

    //Sample items to demonstrate the issue. I want the background to be red if false, blue if true
    Map<String, Boolean> list = new HashMap<>();
    list.put("Item 1", true);
    list.put("Item 2", false);
    list.put("Item 3", true);

    for(Map.Entry<String, Boolean> entry : list.entrySet()){
        String msg = entry.getKey();
        MenuItem item = popupMenu.getMenu().add(msg).setCheckable(true).setChecked(entry.getValue());
        System.out.println(item.getTitle() + ": " + item.isChecked());
    }
    popupMenu.show();
}

styles.xml contains:

<style name="MyPopupMenu" parent="Widget.AppCompat.PopupMenu">
    <item name="android:itemBackground">@drawable/menu_item_background</item>
</style>

actionbar_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
    android:id="@+id/action_bar_button"
    android:title="List"
    android:icon="@drawable/ic_launcher_foreground"
    app:showAsAction="always" />
</menu>

colors.xml contains:
<color name="red">#ff0000</color>
<color name="blue">#0000FF</color>

And the menu_item_background.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/blue" android:state_checked="true"/>
    <item android:drawable="@color/red"  android:state_checked="false"/>
</selector>

However, when I run this I get the following: Actual Output
As you can see although the isChecked state of Item 1 and 3 is true, they still appear red. The logcat output confirms this.

As an experiment I changed the menu_item_background.xml to use the android:state_enabled instead of checked and it works as expected:
enter image description here

What's going on here? Why doesn't this work with android:state_checked?
Thanks for your help.

like image 755
Crompy Avatar asked Oct 24 '18 23:10

Crompy


1 Answers

If your main motive is to highlight the selected item or similar kind of problem. I would like to suggest as one of the solutions which worked for me to use spinner, which Popup Menu provides a similar dropdown option as Spinner, with a custom layout for dropdown, and change the background color at getDropDownView() at the adapter for the spinner.

R.layout.spinner_item.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_context"
        android:layout_height="wrap_content"
        android:visibility="gone"
/>

R.layout.spinner_drop_down.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        >
<TextView
        style="?android:attr/spinnerDropDownItemStyle"
        android:singleLine="true"
        android:layout_width="wrap_content"
        android:layout_height="?attr/dropdownListPreferredItemHeight"
        android:id="@+id/spinner_dropdown_text"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"/>
</LinearLayout>

UI Design

<?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"
        >
     <!--Some Layouts-->
     <Spinner
             android:id="@+id/spinner"
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:layout_gravity="center_vertical"
             android:background="@drawable/icon"
             android:gravity="center"
             android:textDirection="firstStrongLtr"
             android:overlapAnchor="false"/>
     <!--Some More Layouts-->
</LinearLayout>

MainActivity.kt

val spinner = findViewById<Spinner>(R.id.spinner)
val spinnerData = arrayListOf<String>("Item 1", "Item 2", "Item 3")
val arrayAdapter = object : ArrayAdapter<String>(activity!!.applicationContext,R.layout.spinner_item, spinnerData) {
                            override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
                                val spinnerItem = layoutInflater.inflate(R.layout.spinner_drop_down, null)

                                val dropDownText = spinnerItem.findViewById<TextView>(R.id.spinner_dropdown_text)
                                dropDownText.text = spinnerData[position]
                                val selected = spinner.selectedItemPosition
                                if (position == selected) spinnerItem.setBackgroundColor(Color.GRAY)

                                return spinnerItem
                            }
                        }
arrayAdapter.setDropDownViewResource(R.layout.spinner_drop_down)
spinner.adapter = arrayAdapter
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
                                override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
                                    //Do whatever you want to do when Item selected
                                }

                                override fun onNothingSelected(parent: AdapterView<*>) {
                                    //Do whatever you want to do when No Item selected
                                }
                            }

like image 195
Jagarapu Sahruday Avatar answered Nov 15 '22 01:11

Jagarapu Sahruday