一.PopWindow构成3要素
1.contentView window的内容
2.width window的宽度
3.height window的高度
其构造方法:
public PopupWindow(View contentView, int width, int height, boolean focusable);
其实质上是使用WindowManager,在整个窗口之上添加了一个浮动的View。且看其代码:
if(contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
二.show()的过程
public void showAsDropDown(View anchor, int xoff, int yoff)
{
if (isShowing() || mContentView == null)
{return;
}
registerForScrollChanged(anchor, xoff, yoff);
mIsShowing = true;
mIsDropdown = true;
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
preparePopup(p);
updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
p.windowAnimations = computeAnimationResource();
invokePopup(p);
}
第一步:通过createPopupLayout(),创建一个WindowManager.LayoutParams,因为WindowManager在addView()时,需要指定LayoutParams参数,以此来定义窗口的大小。
该方法里只是纯粹的new WindowManager.LayoutParams(),并设置了相关参数。注意这里有个方法computeFlags(),会根据设置的相关属性,如:mFocusable,mTouchable,mOutsideTouchable, mNotTouchModal等计算出相关flag,WindowManager的flag属性,会决定是否拦截touch
event、是否拦截key event,以及touch event, key event的分发等等
第二步:preparePopup(),主要是创建mPopupView(View mPopupView),mPopupView是最终PopupWindow里要显示的内容。
if (mBackground != null)
{
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
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
在这里可以看到是否设置mBackground,对创建mPopupView有很大影响:如果没有设置过mBackground,则mPopupView直接为mContentView;如果设置过mBackground,则会先创建一个PopupViewContainer,其实质上是一个FrameLayout,然后将mContentView填充进去。
PopupViewContainer的作用是什么呢?
其重写了几个方法:
public boolean dispatchKeyEvent(KeyEvent
event);
public boolean dispatchTouchEvent(MotionEvent
ev);
public boolean onTouchEvent(MotionEvent
event);
可以见到,PopupViewContainer会先处理key event、touch event等等,并默认实现了在点击PopupWindow以外的区域,dismiss window的功能。所以要实现点击空白区域,将PopupWindow给dismiss()掉的效果,必须得设置mBackground。如果不设置的话,默认是不会有这些效果的,必须得对mContentView进行重写以上几个方法,才能实现类似的效果。
第三步:updateAboveAnchor(),计算PopupWindow是否要在anchorView之上还是之下显示。
第四步:invokePopup(),使用WindowManager将mPopupView添加到Window当中。
mWindowManager.addView(mPopupView,
p);
三.常用方法
public void setBackgroundDrawable(Drawable
background);
设置window的背景,如果要在点击空白区域能dismiss()的话,必须得设置
public void setTouchModal(boolean touchModal)
设置为true的话,touch event可以穿透该PopupWindow,传递到后面的window,这是个隐藏方法
public void setAnimationStyle(int animationStyle)
设置窗口进出的动画
public void setOutsideTouchable(boolean touchable)
public void setFocusable(boolean focusable)
三.实例
1.不设置任何属性
View view = getLayoutInflater().inflate(R.layout.pop, null);
PopupWindow pw = new PopupWindow(view,
200, ViewGroup.LayoutParams.WRAP_CONTENT);
public boolean hasFocus()
{
return super.hasFocus() || flag;
}
public boolean isFocused() {
return super.isFocused() || flag;
}
public boolean hasWindowFocus() {
return super.hasWindowFocus() || flag;
}
public boolean isInTouchMode() {
return super.isInTouchMode() || flag;
}
pw.showAsDropDown(v);
效果:PopupWindow不会获得焦点,里面的任何控件均得不到响应。区域外面的响应正常。
2.设置focusable属性
View view = getLayoutInflater().inflate(R.layout.pop, null);
PopupWindow pw = new PopupWindow(view,
200, ViewGroup.LayoutParams.WRAP_CONTENT);
pw.setFocusable(true);
pw.showAsDropDown(v);
效果:PopupWindow获得焦点,里面的控件能够获得响应,拦截掉所有touch events, key events等。其区域外面的控件均不会得到响应,返回键也无响应。
3.设置background
View view = getLayoutInflater().inflate(R.layout.pop, null);
PopupWindow pw = new PopupWindow(view,
200, ViewGroup.LayoutParams.WRAP_CONTENT);
pw.setFocusable(true);
pw.setBackgroundDrawable(new BitmapDrawable());
pw.showAsDropDown(v);
效果:PopupWindow获得焦点,里面的控件能够获得响应。按返回键或者是PopupWindow外面的区域,会dismiss掉PopupWindow。
4.设置outsideTouchable属性
View view = getLayoutInflater().inflate(R.layout.pop, null);
PopupWindow pw = new PopupWindow(view,
200, ViewGroup.LayoutParams.WRAP_CONTENT);
pw.setFocusable(true);
pw.setOutsideTouchable(true);
pw.setBackgroundDrawable(new BitmapDrawable());
pw.showAsDropDown(v);
效果:与3的效果一样
5.如何既能让PopupWindow里的控件获得响应,又能让window外的EditText能够响应输入法而能够输入文本呢?
pw.setFocusable(false); //这样window后面的控件,才能获得焦点
pw.setOutsideTouchable(true);
重写PopupWindow的mContentView,使其一直获得焦点
boolean flag = true;
return super.hasFocus() || flag;
}
public boolean isFocused() {
return super.isFocused() || flag;
}
public boolean hasWindowFocus() {
return super.hasWindowFocus() || flag;
}
public boolean isInTouchMode() {
return super.isInTouchMode() || flag;
}