Comment afficher correctement un menu contextuel sous un TextView, similaire à Spinner?

Contexte

Je devais créer une vue de type spinner, qui a ce comportement:

  • TextView qui contient le texte de l'élément sélectionné, avec une icône de flèche à droite qui indique si elle est en état "ouvert".
  • Après avoir cliqué sur la vue, un menu contextuel apparaît au-dessous (pas en haut), montrant une liste d'éléments à choisir, et l'élément sélectionné a également été marqué.
  • Autour de la popupWindow, il existe une couleur noire semi transparente.
  • A une animation personnalisée pour l'ouvrir et la fermer.

Le problème

J'ai réussi à faire cette vue (code ci-dessous), mais pour une raison quelconque, sur Android 7.1, j'ai eu le menu contextuel pour apparaître en haut de la vue (même le chevauchement et les vues ci-dessus), au lieu de le bas, comme il se doit . Voici comment cela se ressemble, et comment il devrait ressembler:

  • Comment ajouter un événement dans Google Calendar à partir de l'activité?
  • Le moyen le plus simple d'utiliser svg dans Android?
  • REST - Comment restreindre l'accès au logiciel client non autorisé
  • Accepter des événements de clic dans RelativeLayout
  • Élément avec l'application: showAsAction not showing
  • Aligner le nom des variables dans le studio android
  • Entrez la description de l'image ici

    Puisqu'il s'agit de beaucoup de code et de ressources, je l'ai tout mis dans un dépôt Github ( ici ), mais voici la partie principale du code (mes tentatives de correction sont dans les commentaires):

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

    Ce que j'ai essayé

    J'ai essayé d'appeler les fonctions suivantes (et les combinaisons), mais aucune n'a aidé:

    • MPopupWindow.setOverlapAnchor (faux);

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

    Pourquoi la fenêtre contextuelle apparaît-elle en haut de la vue? Comment puis-je éviter cela, et j'ai toujours la fenêtre sous la vue, comme cela a été fait avant?

    Est-ce qu'il y a peut-être un bug sur Android 7.1, ce qui provoque ce comportement? Comment puis-je surmonter cela?

  • Existe-t-il un moyen de recevoir une notification lorsque l'utilisateur éteint l'appareil?
  • Convertir une page Pdf en Bitmap en Android Java
  • Modèle MVC sur Android
  • Article simple spinner Android
  • Parallax XY et rotation - calcul de tuiles
  • Idiom pour fermer un curseur
  • 2 Solutions collect form web for “Comment afficher correctement un menu contextuel sous un TextView, similaire à Spinner?”

    OK, je l'ai corrigé en modifiant la mise en page et son code, mais je ne comprends toujours pas pourquoi le code n'a pas bien fonctionné sur Android 7.1.1, mais il a bien fonctionné sur les anciennes versions.

    Voici le code actuel (le dépôt github mis à jour, le code original avec le problème peut être trouvé ici ):

    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> 

    J'essayerais d'éviter d'utiliser PopupWindow et d'essayer d'utiliser android.support.v7.widget.ListPopupWindow place.

    De plus, une des premières choses à vérifier avec de tels problèmes est targetSdk et la version de la bibliothèque appCompat sont à jour et correspondent à la version d'Android sur laquelle vous essayez d'exécuter votre application.

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.