如何在 TextView 下方正确显示类似于 Spinner 的弹出菜单?
How to correctly show a popup menu below a TextView, similar to Spinner?
背景
我必须创建一个类似旋转器的视图,它具有以下行为:
- 其中包含所选项目文本的 textView,右侧有一个箭头图标,指示它是否处于 "open" 状态。
- 单击视图后,其下方(而不是顶部)会出现一个弹出菜单,其中显示了可供选择的项目列表,并且还标记了所选项目。
- popupWindow周围有半透明的黑色。
- 有自定义的打开和关闭动画。
问题
我已经成功制作了这个视图(下面的代码),但出于某种原因,在 Android 7.1 上,我让弹出菜单出现在视图的顶部(甚至重叠它和视图在它上面) ,而不是低于它,因为它应该。这是它的样子,与它应该的样子:
因为它有很多代码和资源,我已经把它全部放在 Github 存储库 (here) 中,但这里是代码的主要部分(我尝试修复它在评论中):
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:
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];
}
};
}
}
我试过的
我尝试调用下一个函数(和组合),但 none 帮助:
mPopupWindow.setOverlapAnchor(假);
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);
问题
为什么弹出窗口 window 出现在视图顶部?我怎样才能避免这种情况,并像以前一样在视图下方保留 window?
Android 7.1 上是否存在导致此行为的错误?我该如何克服这个问题?
我会尽量避免使用 PopupWindow 并尝试使用 android.support.v7.widget.ListPopupWindow
。
对于此类问题,首先要检查的事情之一是 targetSdk 和 appCompat 库版本是最新的,并且与您尝试 运行 您的应用程序的 android 版本相对应.
好的,我已经通过更改布局及其代码修复了它,但我仍然不明白为什么代码在 Android 7.1.1 上运行不佳,但在旧版本上运行良好版本。
这是当前代码(也更新了 github 存储库,可以找到有问题的原始代码 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:
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>
背景
我必须创建一个类似旋转器的视图,它具有以下行为:
- 其中包含所选项目文本的 textView,右侧有一个箭头图标,指示它是否处于 "open" 状态。
- 单击视图后,其下方(而不是顶部)会出现一个弹出菜单,其中显示了可供选择的项目列表,并且还标记了所选项目。
- popupWindow周围有半透明的黑色。
- 有自定义的打开和关闭动画。
问题
我已经成功制作了这个视图(下面的代码),但出于某种原因,在 Android 7.1 上,我让弹出菜单出现在视图的顶部(甚至重叠它和视图在它上面) ,而不是低于它,因为它应该。这是它的样子,与它应该的样子:
因为它有很多代码和资源,我已经把它全部放在 Github 存储库 (here) 中,但这里是代码的主要部分(我尝试修复它在评论中):
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:
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];
}
};
}
}
我试过的
我尝试调用下一个函数(和组合),但 none 帮助:
mPopupWindow.setOverlapAnchor(假);
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);
问题
为什么弹出窗口 window 出现在视图顶部?我怎样才能避免这种情况,并像以前一样在视图下方保留 window?
Android 7.1 上是否存在导致此行为的错误?我该如何克服这个问题?
我会尽量避免使用 PopupWindow 并尝试使用 android.support.v7.widget.ListPopupWindow
。
对于此类问题,首先要检查的事情之一是 targetSdk 和 appCompat 库版本是最新的,并且与您尝试 运行 您的应用程序的 android 版本相对应.
好的,我已经通过更改布局及其代码修复了它,但我仍然不明白为什么代码在 Android 7.1.1 上运行不佳,但在旧版本上运行良好版本。
这是当前代码(也更新了 github 存储库,可以找到有问题的原始代码 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:
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>