很早之前有遇到过一个问题,
点击打开链接就是 PopupWindow 在使用的时候必须要如下设置才可以在点击外部让它消失
而当 mBackgroud == null 的时候
<pre name="code" class="html"> pop.setBackgroundDrawable(new BitmapDrawable());
pop.setOutsideTouchable(true);
pop.showAtLocation();
就是说在设置位置与是否点击消失之前必须要先setBackgroundDrawable();
之前也没有深究过此间缘由,正好今天有时间就追了一下PopupWindow的源码看了下,原来事情是这样的……
我们来看这段代码:
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
unregisterForScrollChanged();
mIsShowing = true;
mIsDropdown = false;
WindowManager.LayoutParams p = createPopupLayout(token);
p.windowAnimations = computeAnimationResource();
<span style="color:#cc0000;">preparePopup(p);</span>
if (gravity == Gravity.NO_GRAVITY) {
gravity = Gravity.TOP | Gravity.START;
}
p.gravity = gravity;
p.x = x;
p.y = y;
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
invokePopup(p);
}
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
registerForScrollChanged(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
<span style="color:#cc0000;">preparePopup(p);</span>
updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
p.windowAnimations = computeAnimationResource();
invokePopup(p);
}
可以看到无乱你调用哪个显示方法都会进入一个
perparePopup(p);的方法。
下面我们来看这个方法做了些什么:
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
<span style="color:#cc0000;">if (mBackground != null)</span> {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
int height = ViewGroup.LayoutParams.MATCH_PARENT;
if (layoutParams != null &&
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
// when a background is available, we embed the content view
// within another view that owns the background drawable
<span style="color:#cc0000;">PopupViewContainer</span> popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} <span style="color:#cc0000;">else</span> {
mPopupView = mContentView;
}
mPopupView.setElevation(mElevation);
mPopupViewInitialLayoutDirectionInherited =
(mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
相信看到这里大家应该能看出点端倪来了吧,是的,它有个内部类 PopupViewContainer ,当 mBackground != null 的时候
mPopupView = popupViewContainer;
而这个类继承于 FrameLayout ,那么下面再接着看这个类又做了什么事情使得必须要设置 mBackground !
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
<span style="color:#cc0000;">dismiss();</span>
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(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()))) {
<span style="color:#cc0000;">dismiss();</span>
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
<span style="color:#cc0000;">dismiss();</span>
return true;
} else {
return super.onTouchEvent(event);
}
}
原因找到了!!它是处理了触摸事件,当点击在view外部的时候都调用了 dismiss() 方法。
而当 mBackgroud == null 的时候
mPopupView = mContentView;
而 mContentView 只是一个 View 的实例
private View mContentView;
由于篇幅原因,接下来会再续前言,而如果只想知道原理 的话那么就可以到此结束了。。
代码下载地址