How to correctly show a popup menu below a TextView, similar to Spinner? How to correctly show a popup menu below a TextView, similar to Spinner? android android

How to correctly show a popup menu below a TextView, similar to Spinner?


OK, I've fixed it by changing the layout and its code, but I still don't get why the code didn't work well on Android 7.1.1, yet worked fine on older versions.

Here is the current code (updated github repository too, original code with the issue can be found here) :

ViewUtil.java

class ViewUtil {    static Drawable getRotateDrawable(final Drawable d, final int angle) {        return new LayerDrawable(new Drawable[]{d}) {            @Override            public void draw(final Canvas canvas) {                canvas.save();                canvas.rotate(angle, d.getBounds().width() / 2, d.getBounds().height() / 2);                super.draw(canvas);                canvas.restore();            }        };    }    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    static ViewPropertyAnimator runOnAnimationEnd(final ViewPropertyAnimator animator, final Runnable runnable) {        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN)            animator.withEndAction(runnable);        else            animator.setListener(new android.animation.Animator.AnimatorListener() {                @Override                public void onAnimationStart(final android.animation.Animator animation) {                }                @Override                public void onAnimationRepeat(final android.animation.Animator animation) {                }                @Override                public void onAnimationEnd(final android.animation.Animator animation) {                    animator.setListener(null);                    runnable.run();                }                @Override                public void onAnimationCancel(final android.animation.Animator animation) {                }            });        return animator;    }}

FullSizePopupSpinner.java

public class FullSizePopupSpinner extends android.support.v7.widget.AppCompatTextView {    private static final long ANIMATION_DURATION = 150;    private int[] mItemsTextsResIds, mItemsIconsResIds;    private int mSelectedItemPosition = -1;    private SpinnerPopupWindow mPopupWindow;    private boolean mInitialized = false;    private OnItemSelectedListener mOnItemSelectedListener;    private Drawable mClosedDrawable;    private Drawable mOpenedDrawable;    public interface OnItemSelectedListener {        void onItemSelected(FullSizePopupSpinner parent, int position, String item, int previousSelectedPosition);        void onNothingSelected(FullSizePopupSpinner parent);    }    public FullSizePopupSpinner(final Context context) {        super(context);        init(context);    }    public FullSizePopupSpinner(final Context context, final AttributeSet attrs) {        super(context, attrs);        init(context);    }    public FullSizePopupSpinner(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.mItemsTextsResIds = mItemsTextsResIds;        ss.mItemsIconsResIds = mItemsIconsResIds;        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.mItemsTextsResIds, ss.mItemsIconsResIds);        setSelectedItemPosition(ss.mSelectedItemPosition);    }    public void setItems(final int[] itemsTextsResIds, final int[] itemsIconsResIds) {        mItemsTextsResIds = itemsTextsResIds;        mItemsIconsResIds = itemsIconsResIds;        if (mItemsTextsResIds != null && mSelectedItemPosition >= 0 && mSelectedItemPosition < mItemsTextsResIds.length)            setText(mItemsTextsResIds[mSelectedItemPosition]);        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, null, null, isPopupShown() ? mOpenedDrawable : mClosedDrawable, null);    }    public boolean isPopupShown() {        return mPopupWindow != null && mPopupWindow.isShowing();    }    public int getSelectedItemPosition() {        return mSelectedItemPosition;    }    public void setSelectedItemPosition(final int selectedItemPosition) {        int lastSelectedItemPosition = mSelectedItemPosition;        mSelectedItemPosition = selectedItemPosition;        final String itemText = mItemsTextsResIds != null && mSelectedItemPosition >= 0 && mSelectedItemPosition < mItemsTextsResIds.length ?                getResources().getString(mItemsTextsResIds[mSelectedItemPosition]) : null;        setText(itemText);        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(FullSizePopupSpinner.this, null, null, mClosedDrawable, null);        if (mOnItemSelectedListener != null)            mOnItemSelectedListener.onItemSelected(FullSizePopupSpinner.this, selectedItemPosition, itemText, lastSelectedItemPosition);    }    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {        mOnItemSelectedListener = onItemSelectedListener;    }    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        if (mPopupWindow != null)            mPopupWindow.dismissRightAway();    }    protected void init(final Context context) {        if (mInitialized)            return;        mInitialized = true;        setSaveEnabled(true);        mClosedDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.drop_down_menu_ic_arrow_down, null);        mOpenedDrawable = ViewUtil.getRotateDrawable(mClosedDrawable, 180);        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(FullSizePopupSpinner.this, null, null, mClosedDrawable, null);        setOnClickListener(new OnClickListener() {            @Override            public void onClick(final View v) {                if (mItemsTextsResIds == null)                    return;                if (mPopupWindow != null)                    mPopupWindow.dismissRightAway();                TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(FullSizePopupSpinner.this, null, null, mOpenedDrawable, null);                final LayoutInflater layoutInflater = LayoutInflater.from(context);                final View popupView = layoutInflater.inflate(R.layout.spinner_drop_down_popup, null, false);                final RecyclerView recyclerView = (RecyclerView) popupView.findViewById(R.id.spinner_drop_down_popup__recyclerView);                final View overlayView = popupView.findViewById(R.id.spinner_drop_down_popup__overlay);                final View itemsContainer = popupView.findViewById(R.id.spinner_drop_down_popup__itemsContainer);                itemsContainer.setPivotY(0);                itemsContainer.setScaleY(0);                itemsContainer.animate().scaleY(1).setDuration(ANIMATION_DURATION).start();                mPopupWindow = new SpinnerPopupWindow(popupView, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, true, overlayView,                        itemsContainer);                mPopupWindow.setOutsideTouchable(true);                mPopupWindow.setTouchable(true);                mPopupWindow.setBackgroundDrawable(new ColorDrawable(0));                final AtomicBoolean isItemSelected = new AtomicBoolean(false);                if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {                    popupView.findViewById(R.id.spinner_drop_down_popup__preLollipopShadow).setVisibility(View.GONE);                    recyclerView.setBackgroundColor(0xFFffffff);                }                recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));                recyclerView.setAdapter(new Adapter() {                    @Override                    public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {                        final View itemView = layoutInflater.inflate(R.layout.spinner_drop_down_popup_item, recyclerView, false);                        final ViewHolder holder = new ViewHolder(itemView) {                        };                        itemView.setOnClickListener(new OnClickListener() {                            @Override                            public void onClick(final View v) {                                isItemSelected.set(true);                                mPopupWindow.dismiss();                                setSelectedItemPosition(holder.getAdapterPosition());                            }                        });                        return holder;                    }                    @Override                    public void onBindViewHolder(final ViewHolder holder, final int position) {                        final String itemText = getResources().getString(mItemsTextsResIds[position]);                        final TextView textView = (TextView) holder.itemView.findViewById(android.R.id.text1);                        textView.setText(itemText);                        if (mItemsIconsResIds != null)                            TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, mItemsIconsResIds[position], 0,                                    position == mSelectedItemPosition ? R.drawable.drop_down_menu_ic_v : 0, 0);                        else                            TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, 0, 0, position == mSelectedItemPosition ? R.drawable.drop_down_menu_ic_v : 0, 0);                    }                    @Override                    public int getItemCount() {                        return mItemsTextsResIds.length;                    }                });                overlayView.setOnClickListener(new OnClickListener() {                    @Override                    public void onClick(final View v) {                        mPopupWindow.dismiss();                    }                });                overlayView.setAlpha(0);                overlayView.animate().alpha(1).setDuration(ANIMATION_DURATION).start();                mPopupWindow.setOnDismissListener(new OnDismissListener() {                    @Override                    public void onDismiss() {                        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(FullSizePopupSpinner.this, null, null, mClosedDrawable, null);                        if (!isItemSelected.get() && mOnItemSelectedListener != null)                            mOnItemSelectedListener.onNothingSelected(FullSizePopupSpinner.this);                    }                });                // optional: set animation style. look here for more info: http://stackoverflow.com/q/9648797/878126                mPopupWindow.setAnimationStyle(0);                PopupWindowCompat.showAsDropDown(mPopupWindow, v, 0, 0, Gravity.TOP);            }        });    }    static class SpinnerPopupWindow extends PopupWindow {        private final View mOverlayView;        private final View mLayout;        public SpinnerPopupWindow(final View contentView, final int width, final int height, final boolean focusable, View overlayView, View layout) {            super(contentView, width, height, focusable);            mOverlayView = overlayView;            mLayout = layout;        }        public void dismissRightAway() {            super.dismiss();        }        @Override        public void dismiss() {            final ViewPropertyAnimator animator = mOverlayView.animate().alpha(0);            mLayout.setPivotY(0);            mLayout.animate().scaleY(0).setDuration(ANIMATION_DURATION);            ViewUtil.runOnAnimationEnd(animator, new Runnable() {                @Override                public void run() {                    dismissRightAway();                }            });            animator.start();        }    }    //////////////////////////////////////    //SavedState//    //////////////    static class SavedState extends BaseSavedState {        private int[] mItemsTextsResIds;        private int mSelectedItemPosition = -1;        public int[] mItemsIconsResIds;        SavedState(Parcelable superState) {            super(superState);        }        private SavedState(@NonNull Parcel in) {            super(in);            this.mItemsTextsResIds = in.createIntArray();            mSelectedItemPosition = in.readInt();            mItemsIconsResIds = in.createIntArray();        }        @Override        public void writeToParcel(@NonNull Parcel out, int flags) {            super.writeToParcel(out, flags);            out.writeIntArray(mItemsTextsResIds);            out.writeInt(mSelectedItemPosition);            out.writeIntArray(mItemsIconsResIds);        }        //required field that makes Parcelables from a Parcel        public static final Creator<SavedState> CREATOR =                new Creator<SavedState>() {                    public SavedState createFromParcel(Parcel in) {                        return new SavedState(in);                    }                    public SavedState[] newArray(int size) {                        return new SavedState[size];                    }                };    }}

spinner_drop_down_popup.xml

<FrameLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:clipToPadding="false">    <View        android:id="@+id/spinner_drop_down_popup__overlay"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="#33000000"/>    <LinearLayout        android:id="@+id/spinner_drop_down_popup__itemsContainer"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical">        <android.support.v7.widget.RecyclerView            android:id="@+id/spinner_drop_down_popup__recyclerView"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:elevation="8dp"            android:gravity="center"/>        <View            android:layout_width="match_parent"            android:layout_height="1dp"            android:background="@drawable/list_view_horizontal_divider"/>        <FrameLayout            android:id="@+id/spinner_drop_down_popup__preLollipopShadow"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:foreground="?android:windowContentOverlay"/>    </LinearLayout></FrameLayout>


I would try to avoid using PopupWindow and try to use android.support.v7.widget.ListPopupWindow instead.

Also one of the first things to check with such a problems is targetSdk and appCompat library version are up to date and correspond to the version of android you are trying to run your application on.