¿Cómo mostrar correctamente un menú emergente debajo de un TextView, similar a Spinner?

Fondo

Tuve que crear un spinner-como vista, que tiene este comportamiento:

  • TextView que tiene el texto del elemento seleccionado en él, con un icono de flecha a la derecha que indica si está en estado "abierto".
  • Al hacer clic en la vista, aparece un menú emergente debajo de él (no en la parte superior), mostrando una lista de elementos para elegir y también tiene el elemento seleccionado marcado.
  • Alrededor del popupWindow, hay un color negro semitransparente.
  • Tiene una animación personalizada para abrirla y cerrarla.

El problema

He conseguido hacer esta vista (código abajo), pero por alguna razón, en Android 7.1, conseguí el menú emergente para aparecer en la parte superior de la opinión (incluso el solapamiento él y opiniones sobre él), en vez de debajo de él, como debe . Así es como se ve, como debería ser:

Introduzca aquí la descripción de la imagen

Ya que es un montón de código y recursos, lo he puesto todo en un repositorio de Github ( aquí ), pero aquí está la parte principal del código (mis intentos de arreglarlo están en los comentarios):

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); LayoutInflater layoutInflater = LayoutInflater.from(context); final View popupView = layoutInflater.inflate(R.layout.spinner_drop_down_popup, null, false); final LinearLayout linearLayout = (LinearLayout) popupView.findViewById(R.id.spinner_drop_down_popup__itemsContainer); final View overlayView = popupView.findViewById(R.id.spinner_drop_down_popup__overlay); linearLayout.setPivotY(0); linearLayout.setScaleY(0); linearLayout.animate().scaleY(1).setDuration(ANIMATION_DURATION).start(); mPopupWindow = new SpinnerPopupWindow(popupView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, true, overlayView, linearLayout); mPopupWindow.setOutsideTouchable(true); mPopupWindow.setTouchable(true); mPopupWindow.setBackgroundDrawable(new ColorDrawable(0)); //PopupWindowCompat.setOverlapAnchor(mPopupWindow, false); //if (VERSION.SDK_INT >= VERSION_CODES.M) // mPopupWindow.setOverlapAnchor(false); 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); linearLayout.setBackgroundColor(0xFFffffff); } for (int i = 0; i < mItemsTextsResIds.length; ++i) { final String itemText = getResources().getString(mItemsTextsResIds[i]); final int position = i; View itemView = layoutInflater.inflate(R.layout.spinner_drop_down_popup_item, linearLayout, false); final TextView textView = (TextView) 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); linearLayout.addView(itemView, linearLayout.getChildCount() - 2); itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { isItemSelected.set(true); mPopupWindow.dismiss(); setSelectedItemPosition(position); } }); } 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); //mPopupWindow.showAsDropDown(v, 0, 0, Gravity.TOP); mPopupWindow.showAsDropDown(v, 0, 0); } }); } 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]; } }; } } 

Lo que he intentado

Intenté llamar a las funciones siguientes (y combinaciones), pero ninguna ayudó:

  • MPopupWindow.setOverlapAnchor (false);

  • PopupWindowCompat.setOverlapAnchor (mPopupWindow, false);

  • MPopupWindow.showAsDropDown (v, 0, 0, Gravity.BOTTOM);

  • PopupWindowCompat.showAsDropDown (mPopupWindow, v, 0, 0, Gravity.BOTTOM);

  • MPopupWindow.showAsDropDown (v, 0, 0, Gravity.TOP);

  • PopupWindowCompat.showAsDropDown (mPopupWindow, v, 0, 0, Gravity.TOP);

La pregunta

¿Por qué aparece la ventana emergente en la parte superior de la vista? ¿Cómo puedo evitar esto, y todavía tengo la ventana debajo de la vista, como se hizo antes?

¿Existe quizá un error en Android 7.1, que causa este comportamiento? ¿Cómo puedo superar esto?

OK, lo he arreglado cambiando el diseño y su código, pero todavía no entiendo por qué el código no funcionó bien en Android 7.1.1, pero funcionó bien en las versiones anteriores.

Aquí está el código actual (el repositorio de github actualizado también, el código original con el problema se puede encontrar aquí ):

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> 

Intentaría evitar utilizar PopupWindow e intentar utilizar android.support.v7.widget.ListPopupWindow .

También una de las primeras cosas para comprobar con tales problemas es targetSdk y appCompat versión de la biblioteca están al día y corresponden a la versión de Android que está tratando de ejecutar su aplicación.

  • ¿Por qué recibo una respuesta vacía cuando mi aplicación android llama a mi API en mi servidor?
  • Cómo establecer la selección de hilandero por el texto dentro de él
  • Vista de datos de Android Spinner que no se actualiza en el elemento re-seleccionado
  • Android.R.simple_spinner_adapter no se puede resolver
  • Establecer el texto de la vista alinear en el centro en el hilandero en Android
  • Cómo utilizar el hilador y llenarlo del arreglo en android
  • Cómo cambiar el color de fondo de una lista de hilanderos en Android
  • Cómo ordenar HashMap como agregado en Android con ArrayAdapter
  • La mejor práctica para implementar el par de valores clave en Android Spinner
  • ¿Cómo quitar la línea del separador en el hilandero?
  • ¿Cómo establecer el valor predeterminado en el menú desplegable de hilandero en Android?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.