如何让 Spinner 项目在被点击时以全宽显示在自身下方,就像在 G+ 应用程序上一样

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

背景

Google plus 有一个类似于旋转器的视图,显示了很多项目,但所有项目都出现在它自己的下方:

我需要在我自己的微调器中模仿这个(这就是我被告知的),但是最近,Material-设计指南说(here)微调器应该把它的项目放在最重要的是,这就是支持库为它所做的。

问题

我找不到恢复此行为的方法。我试过更改 Spinner 的样式,并且还在 Internet 上(和这里)搜索过这个。

问题

  1. 我如何让微调器的项目位于下方(或上方,如果需要),就像之前所做的那样 Material 设计,但就像在 G+ 上一样,以便他们接受全宽?

  2. G+ Spinner 是不是很特别?它有名字吗?指南中的任何地方都提到了吗?也许我可以使用一些东西来代替普通的微调器?

好的,Spinner 如何将项目放在自身下方的解决方案是添加以下内容:

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

似乎甚至可以在 Kitkat 上工作,而不仅仅是在 Lollipop 上,所以我猜它也应该在以前的版本上工作。

但是,我仍然想知道它在 G+ 上是如何工作的,以及是否有 tutorials/samples 我在那里看到的任何东西。

例如,我不知道如何使 window 看起来不像 window。

我试过这个:

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

但这无济于事,因为正确的区域没有被旋转项目覆盖。

我也尝试过使用一个看起来像微调器的 TextView,并为其创建一个 PopupMenu,但我仍然遇到同样的问题:

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>

它不使用样式做任何事情。


完整解决方案

由于按照指示自定义 PopupMenu 非常困难,因此我已经为它制定了完整的解决方案。以下是它的相关部分:

这将触发显示微调器的弹出窗口(实际上充当微调器):

<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: 
                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];
                    }
                };
    }

}

代码就是这些,但也有一些可绘制资源:

应该有一个类似于 "abc_spinner_mtrl_am_alpha" 支持库的 "spinner" 图像文件,并且具有您希望使用的颜色。不确定如何在 pre-Lollipop 上使用色调,因此最好只使用您使用的颜色创建文件。

颜色:

<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>