Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 986
android developer Avatar asked Aug 02 '15 14:08

android developer


People also ask

What are the two events of spinner control in Android?

Spinners provide a quick way to select one value from a set. In the default state, a spinner shows its currently selected value. Touching the spinner displays a dropdown menu with all other available values, from which the user can select a new one.

How can I limit the height of spinner dropdown view in Android?

You can also affect drop down view location and size by subclassing Spinner and overriding its getWindowVisibleDisplayFrame(Rect outRect) which is used by android. widget. PopupWindow for calculations. Just set outRect to limit the area where drop down view can be displayed.

Which is the listener associated with spinner?

Android spinner is associated with AdapterView.


1 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>
like image 197
android developer Avatar answered Oct 13 '22 17:10

android developer