如上图,上下两栏是一个PopupWindow,然后按键之后再弹出一个PopupWindow:
这个新弹出的PopupWindwo只是一个ListView。
但是我发现这个listView无法接收到OnItemClickListener中的响应,查一下PopupWindow的API,有isFocusable()这个函数。测试一下,果然PopupWindow没有获得焦点。
于是在setOnItemClickListener之前先调用listPopupWindow.setFocusable(true);这是按列表项有反应了,但是按屏幕的其他控件都没法接收到touch事件。
搜索了一下,都说只要设置背景就好了。然后我就找了个图片,果然按其他地方弹出来的listview能自动消失。但是为什么呢?
从别的博客找到了对源码的分析得知了答案:http://www.cnblogs.com/mengdd/p/3569127.html
我把关键的部分复制在下面:
1.显示方法
显示提供了两种形式:
showAtLocation()显示在指定位置,有两个方法重载:
public void showAtLocation(View parent, int gravity, int x, int y) public void showAtLocation(IBinder token, int gravity, int x, int y)
showAsDropDown()显示在一个参照物View的周围,有三个方法重载:
public void showAsDropDown(View anchor) public void showAsDropDown(View anchor, int xoff, int yoff) public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
弹出的方法中首先需要preparePopup(),最后再invokePopup()。
prepare的方法中可以看到有无背景的分别:
/** * <p>Prepare the popup by embedding in into a new ViewGroup if the * background drawable is not null. If embedding is required, the layout * parameters' height is mnodified to take into account the background's * padding.</p> * * @param p the layout parameters of the popup's content view */ 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."); } 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.setBackgroundDrawable(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }
2.背景是否为空对Touch事件的影响
如果有背景,则会在contentView外面包一层PopupViewContainer之后作为mPopupView,如果没有背景,则直接用contentView作为mPopupView。
而这个PopupViewContainer是一个内部私有类,它继承了FrameLayout,在其中重写了Key和Touch事件的分发处理:
@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()) { dismiss(); return true; } } return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev); } @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); } }
由于PopupView本身并没有重写Key和Touch事件的处理,所以如果没有包这个外层容器类,点击Back键或者外部区域是不会导致弹框消失的。