Home » Android » android – How to let the Spinner items appear below itself when being clicked and with full width, like on G+ app

android – How to let the Spinner items appear below itself when being clicked and with full width, like on G+ app

Posted by: admin June 15, 2020 Leave a comment

Questions:

background

Google plus has a spinner-like view that shows a lot of items, but all of them appear below itself:

enter image description here

I need to mimic this in my own spinner (that’s what I was told), but as of recently, Material-Design guidelines say (here) the Spinner should put its items on top of itself, and that’s what the support library does for it.

The problem

I can’t find a way to revert this behavior. I’ve tried changing the style of the Spinner, and also searched about this on the Internet (and here).

The questions

  1. How do I let the spinner have its items below (or above instead, if needed), just as was done before Material Design, yet like on G+, so that they take full width?

  2. Is the G+ Spinner a special kind? Does it have a name? Is it mentioned anywhere in the guidelines ? Maybe something that I can use instead of the normal spinner?

How to&Answers:

OK, the solution for the Spinner of how to put the items below itself, is to just add this:

<Spinner
...
android:overlapAnchor="false" />

Seems to work even on Kitkat, and not just on Lollipop, so my guess is that it should work on previous versions too.

However, I still would like to know how it works on G+, and if there are tutorials/samples for whatever it is that I see there.

I don’t know, for example, how to make the window not appear like a window.

I’ve tried this:

android:dropDownWidth="match_parent"
android:popupBackground="#FFffffff" 
android:popupElevation="0px"

but it doesn’t help, as the right area is not covered by the spinner items.

I also tried using a TextView that looks like a spinner, and create a PopupMenu for it, but I still get the same issues:

PopupMenu popupMenu = new PopupMenu(getActivity(), v, Gravity.NO_GRAVITY, R.attr.popupMenuStyle, R.style.PopupMenuFullWidthStyle);

<style name="PopupMenuFullWidthStyle" parent="@style/Widget.AppCompat.PopupMenu">
    <!--<item name="android:dropDownWidth">match_parent</item>-->
    <item name="android:popupBackground">#FFFFFFFF</item>
</style>

It doesn’t do anything using the styles.


Full solution

Since it’s quite hard to customize the PopupMenu the way that I was instructed, I’ve made a full solution for it. Here are the relevant parts of it:

This will trigger showing the spinner’s popup (and in fact act as a spinner) :

<com.example.user.myapplication.FullSizeFakeSpinner
    android:id="@+id/spinner"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/spinner_selector"
    android:gravity="start|center_vertical"
    android:minHeight="44dp"
    android:paddingLeft="8dp"
    android:paddingRight="8dp"
    android:text="Fake Spinner"
    tools:ignore="UnusedAttribute"/>

MainActivity.java
    private static final String[] ITEMS = {“Item 0”, “Item 1”};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    FullSizeFakeSpinner spinner = (FullSizeFakeSpinner) findViewById(R.id.spinner);
    spinner.setItems(ITEMS);
    }

FullSizeFakeSpinner

public class FullSizeFakeSpinner extends TextView {
    private String[] mItems;
    private int mSelectedItemPosition = -1;
    private PopupWindow mPopupWindow;
    private boolean mInitialized = false;
    private OnItemClickListener mOnItemSelectedListener;

    public interface OnItemClickListener {
        void onItemClick(FullSizeFakeSpinner parent, View clickedView, int position, String item);

        void onNothingSelected(FullSizeFakeSpinner parent);
    }

    public FullSizeFakeSpinner(final Context context) {
        super(context);
        init(context);
    }

    public FullSizeFakeSpinner(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public FullSizeFakeSpinner(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.mSelectedItemPosition = this.mSelectedItemPosition;
        ss.mItems = mItems;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        setItems(ss.mItems);
        setSelectedItemPosition(ss.mSelectedItemPosition);
    }

    public String[] getItems() {
        return mItems;
    }

    public void setItems(final String[] items) {
        mItems = items;
        if (mItems != null && mSelectedItemPosition >= 0 && mSelectedItemPosition < mItems.length)
            setText(mItems[mSelectedItemPosition]);
    }

    public int getSelectedItemPosition() {
        return mSelectedItemPosition;
    }

    public void setSelectedItemPosition(final int selectedItemPosition) {
        mSelectedItemPosition = selectedItemPosition;
        if (mItems != null && mSelectedItemPosition >= 0 && mSelectedItemPosition < mItems.length)
            setText(mItems[mSelectedItemPosition]);
    }

    public void setOnItemSelectedListener(OnItemClickListener onItemSelectedListener) {
        mOnItemSelectedListener = onItemSelectedListener;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mPopupWindow != null)
            mPopupWindow.dismiss();
    }


    protected void init(final Context context) {
        if (mInitialized)
            return;
        mInitialized = true;
        setSaveEnabled(true);
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (mItems == null)
                    return;
                LayoutInflater layoutInflater = LayoutInflater.from(context);
                final View popupView = layoutInflater.inflate(R.layout.spinner_drop_down_popup, null, false);
                final LinearLayout linearLayout = (LinearLayout) popupView.findViewById(android.R.id.list);
                linearLayout.setOrientation(LinearLayout.VERTICAL);
                mPopupWindow = new PopupWindow(popupView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                mPopupWindow.setOutsideTouchable(true);
                mPopupWindow.setTouchable(true);
                mPopupWindow.setBackgroundDrawable(new ColorDrawable(0));
                mPopupWindow.setFocusable(true);
                final AtomicBoolean isItemSelected = new AtomicBoolean(false);
                for (int i = 0; i < mItems.length; ++i) {
                    final String item = mItems[i];
                    final int position = i;
                    View itemView = layoutInflater.inflate(android.R.layout.simple_list_item_1, linearLayout, false);
                    itemView.setBackgroundResource(R.drawable.listview_white_selector);
                    itemView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
                    ((TextView) itemView.findViewById(android.R.id.text1)).setText(item);
                    linearLayout.addView(itemView, linearLayout.getChildCount() - 1);
                    itemView.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(final View v) {
                            isItemSelected.set(true);
                            mPopupWindow.dismiss();
                            mSelectedItemPosition = position;
                            setText(item);
                            if (mOnItemSelectedListener != null)
                                mOnItemSelectedListener.onItemClick(FullSizeFakeSpinner.this, v, position, item);
                        }
                    });
                }
                popupView.findViewById(android.R.id.empty).setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(final View v) {
                        mPopupWindow.dismiss();
                    }
                });
                mPopupWindow.setOnDismissListener(new OnDismissListener() {
                    @Override
                    public void onDismiss() {
                        setViewBackgroundWithoutResettingPadding(FullSizeFakeSpinner.this, R.drawable.spinner_selector);
                        if (!isItemSelected.get() && mOnItemSelectedListener != null)
                            mOnItemSelectedListener.onNothingSelected(FullSizeFakeSpinner.this);
                    }
                });
                // optional: set animation style. look here for more info: http://stackoverflow.com/q/9648797/878126
                mPopupWindow.showAsDropDown(v, 0, 0);
                setViewBackgroundWithoutResettingPadding(FullSizeFakeSpinner.this, R.drawable.spinner_opened_selector);
            }
        });

    }

    public static void setViewBackgroundWithoutResettingPadding(final View v, final int backgroundResId) {
        final int paddingBottom = v.getPaddingBottom(), paddingLeft = v.getPaddingLeft();
        final int paddingRight = v.getPaddingRight(), paddingTop = v.getPaddingTop();
        v.setBackgroundResource(backgroundResId);
        v.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }

    //////////////////////////////////////
    //SavedState//
    //////////////
    static class SavedState extends BaseSavedState {
        private String[] mItems;
        private int mSelectedItemPosition = -1;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.mItems = in.createStringArray();
            mSelectedItemPosition = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeStringArray(mItems);
            out.writeInt(mSelectedItemPosition);
        }

        //required field that makes Parcelables from a Parcel
        public static final Parcelable.Creator<SavedState> CREATOR =
                new Parcelable.Creator<SavedState>() {
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }

                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
    }

}

That’s it about the code, but there are also some drawable resources too:

There should be a “spinner” image file that’s like “abc_spinner_mtrl_am_alpha” of the support library, and has the color that you wish to use. Not sure how to use tint for it on pre-Lollipop, so it’s better to just create the file with the color that you use.

colors:

<color name="listview_pressed">#FFE2E2E2</color>
<color name="listview_focused">#FF7dbcd3</color>
<color name="listview_checked">#FFededed</color>

listview_white_selector.xml :

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true"><shape>
        <solid android:color="@color/listview_pressed" />
    </shape></item>
    <item android:state_focused="true"><shape>
        <solid android:color="@color/listview_focused" />
    </shape></item>
    <item android:state_checked="true"><shape>
        <solid android:color="@color/listview_checked" />
    </shape></item>
    <item android:state_selected="true"><shape>
        <solid android:color="@color/listview_checked" />
    </shape></item>
    <item android:drawable="@android:color/white"/>

</selector>

listview_white_selector.xml v21 :

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/listview_pressed">

    <item android:id="@android:id/mask">
        <color
            android:id="@android:id/mask"
            android:color="@color/listview_pressed"/>
    </item>
    <item android:drawable="@drawable/listview_ripple_white_background_selector"/>
</ripple>

spinner_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <selector xmlns:android="http://schemas.android.com/apk/res/android">
            <item android:state_pressed="true">
                <shape>
                    <solid android:color="@color/listview_pressed"/>
                </shape>
            </item>
            <item android:state_focused="true">
                <shape>
                    <solid android:color="@color/listview_focused"/>
                </shape>
            </item>
            <item android:state_checked="true">
                <shape>
                    <solid android:color="@color/listview_checked"/>
                </shape>
            </item>
            <item android:state_selected="true">
                <shape>
                    <solid android:color="@color/listview_checked"/>
                </shape>
            </item>
            <item android:drawable="@android:color/transparent"/>
        </selector>
    </item>

    <item android:drawable="@drawable/spinner"/>

</layer-list>

spinner_selector.xml (v21)

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/listview_pressed">
    <item android:drawable="@drawable/spinner"/>
    <item android:id="@android:id/mask">
        <color
            android:id="@android:id/mask"
            android:color="@color/listview_pressed"/>
    </item>
    <item android:drawable="@drawable/listview_ripple_background_selector"/>
</ripple>

listview_ripple_white_background_selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true"><shape>
        <solid android:color="@color/listview_checked" />
    </shape></item>
    <item android:state_selected="true"><shape>
        <solid android:color="@color/listview_checked" />
    </shape></item>
    <item android:drawable="@android:color/white"/>

</selector>

listview_ripple_background_selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true"><shape>
            <solid android:color="@color/listview_checked" />
        </shape></item>
    <item android:state_selected="true"><shape>
            <solid android:color="@color/listview_checked" />
        </shape></item>
    <item android:drawable="@android:color/transparent"/>

</selector>

spinner_drop_down_popup

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <LinearLayout
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">

        <View
            android:id="@android:id/empty"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#33000000"/>
    </LinearLayout>
</ScrollView>