PopupWindow 无法在点击外部后自动消失的问题
最近在 Android 5.x 上遇到了一个 PopupWindow 无法消失的问题,PopupWindow 已经设置了
popup.setOutsideTouchable(true);
popup.setOutsideTouchable(true);
popup.setOutsideTouchable(true);
但点击 PopupWindow 外部无法消除 PopupWindow。在 Android 6.0 上没有此问题。
该问题的原因是在使用PopupWindow时,为了取消默认的带阴影效果,使用了
popup.setBackgroundDrawable(null);
popup.setBackgroundDrawable(null);
popup.setBackgroundDrawable(null);
使用非 null 的 Background 即可解决此问题,如:
popup.setBackgroundDrawable(
new ColorDrawable(ContextCompat.getColor(this, R.color.popupBackground)));
popup.setBackgroundDrawable(
new ColorDrawable(ContextCompat.getColor(this, R.color.popupBackground)));
popup.setBackgroundDrawable( new ColorDrawable(ContextCompat.getColor(this, R.color.popupBackground)));
查看 Android 5.1 版本中的 PopupWindow 可以看到,在 preparePopup() 阶段,会检查 mBackground 是否为 null,若不为 null,则会生成一个 PopupViewContainer:
private void preparePopup(WindowManager.LayoutParams p) {
// ...
if (mBackground != null) {
// ...
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
// ...
}
private void preparePopup(WindowManager.LayoutParams p) {
// ...
if (mBackground != null) {
// ...
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
// ...
}
private void preparePopup(WindowManager.LayoutParams p) { // ... if (mBackground != null) { // ... // when a background is available, we embed the content view // within another view that owns the background drawable PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } // ... }
在同文件中可以找到 PopupViewContainer,它会在点击 PopupWindow 外部后调用 dismiss():
@Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
@Override public boolean onTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); return true; } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } else { return super.onTouchEvent(event); } }
由此可见,如果 PopupWindow 的 Background 为 null,没有 PopupViewContainer,PopupWindow 就不会在点击外部时自动消失。
Android 6.0 的代码中,在 preparePopup() 中可以找到:
private void preparePopup(WindowManager.LayoutParams p) {
// ...
// When a background is available, we embed the content view within
// another view that owns the background drawable.
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
mDecorView = createDecorView(mBackgroundView);
// ...
}
private void preparePopup(WindowManager.LayoutParams p) {
// ...
// When a background is available, we embed the content view within
// another view that owns the background drawable.
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
mDecorView = createDecorView(mBackgroundView);
// ...
}
private void preparePopup(WindowManager.LayoutParams p) { // ... // When a background is available, we embed the content view within // another view that owns the background drawable. if (mBackground != null) { mBackgroundView = createBackgroundView(mContentView); mBackgroundView.setBackground(mBackground); } else { mBackgroundView = mContentView; } mDecorView = createDecorView(mBackgroundView); // ... }
点击外部取消的逻辑放到了 PopupDecorView 中,也就是上面的 mDecorView,由 createDecorView() 创建,与 mBackground 是否为 null 无关。所以在 Android 6.0 上,即使 Background 为 null,PopupWindow 依旧可以正常消失。