PopUpWindow使用详解(二)——进阶及答疑

来源:互联网 发布:淘宝网主要栏目 编辑:程序博客网 时间:2024/05/18 01:31

前言:有人问我,即便梦想成真了又能怎样,或许不能怎样,但这是梦想。


相关文章:
1、《PopUpWindow使用详解(一)——基本使用》
2、《PopUpWindow使用详解(二)——进阶及答疑》


上篇为大家基本讲述了有关PopupWindow的基本使用,但还有几个相关函数还没有讲述,我们这篇将着重看看这几个函数的用法并结合源码来讲讲具体原因,最后是有关PopupWindow在使用时的疑问,给大家讲解一下。

一、常用函数讲解

这段将会给大家讲下下面几个函数的意义及用法,使用上篇那个带背景的例子为基础。

[java] view plain copy
print?
  1. public void setTouchable(boolean touchable)  
  2. public void setFocusable(boolean focusable)  
  3. public void setOutsideTouchable(boolean touchable)  
  4. public void setBackgroundDrawable(Drawable background)  
public void setTouchable(boolean touchable)public void setFocusable(boolean focusable)public void setOutsideTouchable(boolean touchable)public void setBackgroundDrawable(Drawable background)

1、setTouchable(boolean touchable)

设置PopupWindow是否响应touch事件,默认是true,如果设置为false,即会是下面这个结果:(所有touch事件无响应,包括点击事件)


对应代码:

[java] view plain copy
print?
  1. private void showPopupWindow() {  
  2.     View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  3.     mPopWindow = new PopupWindow(contentView);  
  4.     mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  5.     mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
  6.   
  7.     mPopWindow.setTouchable(false);  
  8.   
  9.     ………………//单项点击  
  10.   
  11.     mPopWindow.showAsDropDown(mMenuTv);  
  12. }  
private void showPopupWindow() {    View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);    mPopWindow = new PopupWindow(contentView);    mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setTouchable(false);    ………………//单项点击    mPopWindow.showAsDropDown(mMenuTv);}

2、setFocusable(boolean focusable)

该函数的意义表示,PopupWindow是否具有获取焦点的能力,默认为False。一般来讲是没有用的,因为普通的控件是不需要获取焦点的,而对于EditText则不同,如果不能获取焦点,那么EditText将是无法编辑的。
所以,我们在popuplayout.xml最底部添加一个EditText,分别演示两段不同的代码,即分别将setFocusable设置为false和设置为true;看看有什么不同:
(1)setFocusable(true)
代码如下:

[html] view plain copy
print?
  1. private void showPopupWindow() {  
  2.     View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  3.     mPopWindow = new PopupWindow(contentView);  
  4.     mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  5.     mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
  6.   
  7.     //是否具有获取焦点的能力  
  8.     mPopWindow.setFocusable(true);  
  9.   
  10.    …………//各item点击响应  
  11.   
  12.   
  13.     mPopWindow.showAsDropDown(mMenuTv);  
  14. }  
private void showPopupWindow() {    View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);    mPopWindow = new PopupWindow(contentView);    mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);    //是否具有获取焦点的能力    mPopWindow.setFocusable(true);   …………//各item点击响应    mPopWindow.showAsDropDown(mMenuTv);}
明显在点击EditText的时候,会弹出编辑框。


(2)setFocusable(false)
同样上面一段代码,那我们将setFocusable设置为false,会是怎样呢?

[java] view plain copy
print?
  1. private void showPopupWindow() {  
  2.     View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  3.     mPopWindow = new PopupWindow(contentView);  
  4.     mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  5.     mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
  6.   
  7.     //是否具有获取焦点的能力  
  8.     mPopWindow.setFocusable(false);  
  9.   
  10.    …………//各item点击响应  
  11.   
  12.   
  13.     mPopWindow.showAsDropDown(mMenuTv);  
  14. }  
private void showPopupWindow() {    View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);    mPopWindow = new PopupWindow(contentView);    mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);    //是否具有获取焦点的能力    mPopWindow.setFocusable(false);   …………//各item点击响应    mPopWindow.showAsDropDown(mMenuTv);}
效果图下:
可见,点击EditText没有出现任何反应!所以如果PopupWindow没有获取焦点的能力,那么它其中的EditText当然是没办法获取焦点的,EditText无法获取焦点,那对它而言整个EditText控件就是不可用的。

3、setOutsideTouchable(boolean touchable)

这个函数的意义,就是指,PopupWindow以外的区域是否可点击,即如果点击PopupWindow以外的区域,PopupWindow是否会消失。
下面这个是点击会消息的效果图:


看看它对应的代码:

[java] view plain copy
print?
  1. private void showPopupWindow() {  
  2.     View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  3.     mPopWindow = new PopupWindow(contentView);  
  4.     mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  5.     mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
  6.   
  7.     //外部是否可以点击  
  8.     mPopWindow.setBackgroundDrawable(new BitmapDrawable());  
  9.     mPopWindow.setOutsideTouchable(true);  
  10.   
  11.     …………//各ITEM点击响应  
  12.   
  13.     mPopWindow.showAsDropDown(mMenuTv);  
  14.   
  15. }  
private void showPopupWindow() {    View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);    mPopWindow = new PopupWindow(contentView);    mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);    //外部是否可以点击    mPopWindow.setBackgroundDrawable(new BitmapDrawable());    mPopWindow.setOutsideTouchable(true);    …………//各ITEM点击响应    mPopWindow.showAsDropDown(mMenuTv);}
这里要非常注意的一点:
[java] view plain copy
print?
  1. mPopWindow.setBackgroundDrawable(new BitmapDrawable());  
  2. mPopWindow.setOutsideTouchable(true);  
mPopWindow.setBackgroundDrawable(new BitmapDrawable());mPopWindow.setOutsideTouchable(true);
大家可能要疑问,为什么要加上mPopWindow.setBackgroundDrawable(new BitmapDrawable());这句呢,从代码来看没并没有真正设置Bitmap,而只是new了一个空的bitmap,好像并没起到什么作用。那如果我们把这句去掉会怎样:
把代码改成这样子:(只使用setOutsideTouchable)
[java] view plain copy
print?
  1. private void showPopupWindow() {  
  2.     View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  3.     mPopWindow = new PopupWindow(contentView);  
  4.     mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  5.     mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
  6.   
  7.     //外部是否可以点击  
  8.     mPopWindow.setOutsideTouchable(true);  
  9.   
  10.     …………//各ITEM点击响应  
  11.   
  12.     mPopWindow.showAsDropDown(mMenuTv);  
  13. }  
private void showPopupWindow() {    View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);    mPopWindow = new PopupWindow(contentView);    mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);    mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);    //外部是否可以点击    mPopWindow.setOutsideTouchable(true);    …………//各ITEM点击响应    mPopWindow.showAsDropDown(mMenuTv);}

看到了没,点击外部没反应………………这就有点坑了,至于原因,我们在setBackgroundDrawable()中讲。

4、setBackgroundDrawable(Drawable background)

这个函数可是吊了,这个函数不只能设置背景……,因为你加上它之后,setOutsideTouchable()才会生效;

而且,只有加上它之后,PopupWindow才会对手机的返回按钮有响应:即,点击手机返回按钮,可以关闭PopupWindow;如果不加setBackgroundDrawable()将关闭的PopupWindow所在的Activity.
这个函数要怎么用,这里应该就不用讲了吧,可以填充进去各种Drawable,比如new BitmapDrawable(),new ColorDrawable(),等;
我们这里主要从源码的角度来看看setBackgroundDrawable()后,PopupWindow都做了些什么。
首先看看setBackgroundDrawable(),将传进去的background赋值给mBackground;

[java] view plain copy
print?
  1. void setBackgroundDrawable(Drawable background) {  
  2.     mBackground = background;  
  3. }  
void setBackgroundDrawable(Drawable background) {    mBackground = background;}
然后再看看显示showAsDropDown()显示的时候,都做了些什么。代码如下:
[java] view plain copy
print?
  1. public void showAsDropDown(View anchor, int xoff, int yoff) {  
  2.     …………  
  3.     //准备窗口  
  4.     WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());  
  5.     preparePopup(p);  
  6.   
  7.     …………  
  8.     //显示窗口  
  9.     invokePopup(p);  
  10. }  
public void showAsDropDown(View anchor, int xoff, int yoff) {    …………    //准备窗口    WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());    preparePopup(p);    …………    //显示窗口    invokePopup(p);}
在这段代码中,先是准备窗口用来显示,然后再利用invokePopup()来显示窗体。
我们看看在preparePopup(p)中是怎么准备窗体的:
[html] view plain copy
print?
  1. private void preparePopup(WindowManager.LayoutParams p) {  
  2.     if (mBackground != null) {  
  3.         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();  
  4.         int height = ViewGroup.LayoutParams.MATCH_PARENT;  
  5.         if (layoutParams != null &&  
  6.                 layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {  
  7.             height = ViewGroup.LayoutParams.WRAP_CONTENT;  
  8.         }  
  9.   
  10.         // when a background is available, we embed the content view  
  11.         // within another view that owns the background drawable  
  12.         PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);  
  13.         PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(  
  14.                 ViewGroup.LayoutParams.MATCH_PARENT, height  
  15.         );  
  16.         popupViewContainer.setBackgroundDrawable(mBackground);  
  17.         popupViewContainer.addView(mContentView, listParams);  
  18.   
  19.         mPopupView = popupViewContainer;  
  20.     } else {  
  21.         mPopupView = mContentView;  
  22.     }  
  23.     mPopupWidth = p.width;  
  24.     mPopupHeight = p.height;  
  25. }  
private void preparePopup(WindowManager.LayoutParams p) {    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;    }    mPopupWidth = p.width;    mPopupHeight = p.height;}
从上面可以看出,如果mBackground不这空,会首先生成一个PopupViewContainer的ViewContainer,然后把mContentView做为子布局add进去,然后把popupViewContainer做为PopupWindow做为根布局。
[html] view plain copy
print?
  1. popupViewContainer.addView(mContentView, listParams);  
popupViewContainer.addView(mContentView, listParams);
那如果mBackground不为空,那就直接把mContentView做为View传递给PopupWindow窗体。
[java] view plain copy
print?
  1. mPopupView = mContentView  
mPopupView = mContentView
到此,我们知道,如果mBackground不为空,会在我们设置的contentView外再包一层布局。
那下面,我们再看看包的这层布局都干了什么:
先列出来完整的代码,然后再分步讲(已做精简,如需知道更多,可参看源码)

[java] view plain copy
print?
  1. private class PopupViewContainer extends FrameLayout {  
  2.     private static final String TAG = “PopupWindow.PopupViewContainer”;  
  3.   
  4.     public PopupViewContainer(Context context) {  
  5.         super(context);  
  6.     }  
  7.     …………  
  8.    @Override  
  9.    public boolean dispatchKeyEvent(KeyEvent event) {  
  10.        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {  
  11.            if (event.getAction() == KeyEvent.ACTION_DOWN  
  12.                    && event.getRepeatCount() == 0) {  
  13.                …………  
  14.            } else if (event.getAction() == KeyEvent.ACTION_UP) {  
  15.                KeyEvent.DispatcherState state = getKeyDispatcherState();  
  16.                if (state != null && state.isTracking(event) && !event.isCanceled()) {  
  17.                    dismiss();  
  18.                    return true;  
  19.                }  
  20.            }  
  21.            return super.dispatchKeyEvent(event);  
  22.        } else {  
  23.            return super.dispatchKeyEvent(event);  
  24.        }  
  25.    }  
  26.   
  27.     @Override  
  28.     public boolean onTouchEvent(MotionEvent event) {  
  29.         final int x = (int) event.getX();  
  30.         final int y = (int) event.getY();  
  31.           
  32.         if ((event.getAction() == MotionEvent.ACTION_DOWN)  
  33.                 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {  
  34.             dismiss();  
  35.             return true;  
  36.         } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {  
  37.             dismiss();  
  38.             return true;  
  39.         } else {  
  40.             return super.onTouchEvent(event);  
  41.         }  
  42.     }  
  43.     …………  
  44. }  
private class PopupViewContainer extends FrameLayout {    private static final String TAG = "PopupWindow.PopupViewContainer";    public PopupViewContainer(Context context) {        super(context);    }    …………   @Override   public boolean dispatchKeyEvent(KeyEvent event) {       if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {           if (event.getAction() == KeyEvent.ACTION_DOWN                   && event.getRepeatCount() == 0) {               …………           } 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 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);        }    }    …………}
这里总共需要注意三部分:
(1)、PopupViewContainer派生自FrameLayout
从PopupViewContainer声明上可以看到,PopupViewContainer派生自FrameLayout;所以,这也是它能将我们传进来的contentView添加为自己的子布局的原因。
(2)、返回按钮捕捉
[java] view plain copy
print?
  1. public boolean dispatchKeyEvent(KeyEvent event) {  
  2.    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {  
  3.        if (event.getAction() == KeyEvent.ACTION_DOWN  
  4.                && event.getRepeatCount() == 0) {  
  5.            …………  
  6.        } else if (event.getAction() == KeyEvent.ACTION_UP) {  
  7.            //抬起手指时  
  8.            KeyEvent.DispatcherState state = getKeyDispatcherState();  
  9.            if (state != null && state.isTracking(event) && !event.isCanceled()) {  
  10.                //隐藏窗体  
  11.                dismiss();  
  12.                return true;  
  13.            }  
  14.        }  
  15.        return super.dispatchKeyEvent(event);  
  16.    } else {  
  17.        return super.dispatchKeyEvent(event);  
  18.    }  
  19. }  
public boolean dispatchKeyEvent(KeyEvent event) {   if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {       if (event.getAction() == KeyEvent.ACTION_DOWN               && event.getRepeatCount() == 0) {           …………       } 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);   }}
从上面的代码来看,PopupViewContainer捕捉了KeyEvent.KEYCODE_BACK事件,并且在用户在点击back按钮,抬起手指的时候(event.getAction() == KeyEvent.ACTION_UP)将窗体隐藏掉。
所以,添加上mBackground以后,可以在用户点击返回按钮时,隐藏窗体!
(3)、捕捉Touch事件——onTouchEvent
[java] view plain copy
print?
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     final int x = (int) event.getX();  
  3.     final int y = (int) event.getY();  
  4.       
  5.     if ((event.getAction() == MotionEvent.ACTION_DOWN)  
  6.             && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {  
  7.         dismiss();  
  8.         return true;  
  9.     } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {  
  10.         dismiss();  
  11.         return true;  
  12.     } else {  
  13.         return super.onTouchEvent(event);  
  14.     }  
  15. }  
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);    }}

从这代码来看,PopupViewContainer捕捉了两种touch事件,MotionEvent.ACTION_DOWN和MotionEvent.ACTION_OUTSIDE;将接收到这两个事件时,会将窗体隐藏掉。
MotionEvent.ACTION_DOWN的触发很好理解,即当用户点击到PopupViewContainer事件时,就隐藏掉;
所以,下面的情况就来了:
假如有一个TextView,我们没有对它设置点击响应。那只要加了background,那点击事件就会传给下层的PopupViewContainer,从而使窗体消失。
那还有个问题,MotionEvent.ACTION_OUTSIDE是个什么鬼?它是怎么触发的。我们在下面开一段细讲。
(4)MotionEvent.ACTION_OUTSIDE与setOutsideTouchable(boolean touchable)
可能把这两个放在一块,大家可能就恍然大悟了,表着急,一个个来看。
先看看setOutsideTouchable(boolean touchable)的代码:

[java] view plain copy
print?
  1. public void setOutsideTouchable(boolean touchable) {  
  2.    mOutsideTouchable = touchable;  
  3. }  
public void setOutsideTouchable(boolean touchable) {   mOutsideTouchable = touchable;}
然后再看看mOutsideTouchable哪里会用到
下面代码,我做了严重精减,等下会再完整再讲这一块
[java] view plain copy
print?
  1. private int computeFlags(int curFlags) {  
  2.     curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);  
  3.     …………  
  4.     if (mOutsideTouchable) {  
  5.         curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;  
  6.     }  
  7.     …………  
  8.     return curFlags;  
  9. }  
private int computeFlags(int curFlags) {    curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);    …………    if (mOutsideTouchable) {        curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;    }    …………    return curFlags;}
这段代码主要是用各种变量来设置window所使用的flag;
首先,将curFlags所在运算的各种Flag,全部置为False;代码如下:
[java] view plain copy
print?
  1. curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);  
curFlags &= ~(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
然后,再根据用户设置的变量来开启:
[java] view plain copy
print?
  1. if (mOutsideTouchable) {  
  2.    curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;  
  3. }  
if (mOutsideTouchable) {   curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;}
既然讲到FLAG_WATCH_OUTSIDE_TOUCH,那我们来看看FLAG_WATCH_OUTSIDE_TOUCH所代表的意义:


这段话的意思是说,如果窗体设置了FLAG_WATCH_OUTSIDE_TOUCH这个flag,那么 用户点击窗体以外的位置时,将会在窗体的MotionEvent中收到MotionEvetn.ACTION_OUTSIDE事件。
参见:《WindowManager.LayoutParams》

所以在PopupViewContainer中添加了对MotionEvent.ACTION_OUTSIDE的捕捉!当用户点击PopupViewContainer以外的区域时,将dismiss掉PopupWindow

[java] view plain copy
print?
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     final int x = (int) event.getX();  
  3.     final int y = (int) event.getY();  
  4.       
  5.     if ((event.getAction() == MotionEvent.ACTION_DOWN)  
  6.             && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {  
  7.         dismiss();  
  8.         return true;  
  9.     } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {  
  10.         dismiss();  
  11.         return true;  
  12.     } else {  
  13.         return super.onTouchEvent(event);  
  14.     }  
  15. }  
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);    }}
(5)重看PopupWindow的computeFlags(int curFlags)函数
完整的computeFlags()函数如下:
[java] view plain copy
print?
  1. private int computeFlags(int curFlags) {  
  2.     curFlags &= ~(  
  3.             WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |  
  4.             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  
  5.             WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |  
  6.             WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |  
  7.             WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |  
  8.             WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |  
  9.             WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);  
  10.     if(mIgnoreCheekPress) {  
  11.         curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;  
  12.     }  
  13.     if (!mFocusable) {  
  14.         curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;  
  15.         if (mInputMethodMode == INPUT_METHOD_NEEDED) {  
  16.             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;  
  17.         }  
  18.     } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {  
  19.         curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;  
  20.     }  
  21.     if (!mTouchable) {  
  22.         curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;  
  23.     }  
  24.     if (mOutsideTouchable) {  
  25.         curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;  
  26.     }  
  27.     if (!mClippingEnabled) {  
  28.         curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;  
  29.     }  
  30.     if (isSplitTouchEnabled()) {  
  31.         curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;  
  32.     }  
  33.     if (mLayoutInScreen) {  
  34.         curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;  
  35.     }  
  36.     if (mLayoutInsetDecor) {  
  37.         curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;  
  38.     }  
  39.     if (mNotTouchModal) {  
  40.         curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;  
  41.     }  
  42.     return curFlags;  
  43. }  
private int computeFlags(int curFlags) {    curFlags &= ~(            WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |            WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);    if(mIgnoreCheekPress) {        curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;    }    if (!mFocusable) {        curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;        if (mInputMethodMode == INPUT_METHOD_NEEDED) {            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;        }    } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {        curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;    }    if (!mTouchable) {        curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;    }    if (mOutsideTouchable) {        curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;    }    if (!mClippingEnabled) {        curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;    }    if (isSplitTouchEnabled()) {        curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;    }    if (mLayoutInScreen) {        curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;    }    if (mLayoutInsetDecor) {        curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;    }    if (mNotTouchModal) {        curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;    }    return curFlags;}
这段代码同样是分两段:
第一段:将所有要计算的FLAG,全部在结果curFlags中置为FALSE;
[java] view plain copy
print?
  1. curFlags &= ~(  
  2.         WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |  
  3.         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  
  4.         WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |  
  5.         WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |  
  6.         WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |  
  7.         WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |  
  8.         WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);  
curFlags &= ~(        WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |        WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |        WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |        WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
第二段:然后根据用户设置的变量,逐个判断是否打开。比如下面这个:
[java] view plain copy
print?
  1. //看到了吧,我们的setTouchable(boolean touchable)最终也是通过在这里设置的  
  2. if (!mTouchable) {  
  3.     curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;  
  4. }  
//看到了吧,我们的setTouchable(boolean touchable)最终也是通过在这里设置的if (!mTouchable) {    curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}
好了,结合源码就给大家讲到这里了。最后总结一下:
**如果我们给PopupWindow添加了mBackground,那它将会:**
  • setOutsideTouchable(true)将生效,具有外部点击隐藏窗体的功能
  • 手机上的返回键将可以使窗体消失
  • 对于PopupWindow上层没有捕捉的点击事件,点击之后,仍然能使窗体消失。
源码在文章底部给出

二、为什么要强制代码设置PopupWindow的Height、Width

在上篇,我们留了这么个疑问,设置contentView很容易理解,但width和height为什么要强制设置呢?我们在布局代码中不是已经写的很清楚了么?比如我们的popuplayout.xml的根布局:

[html] view plain copy
print?
  1. <RelativeLayout  
  2.         xmlns:android=“http://schemas.android.com/apk/res/android”  
  3.         android:layout_width=“fill_parent”  
  4.         android:layout_height=“fill_parent”  
  5.         android:background=“#66000000”>  
  6.         …………  
  7. </RelativeLayout>     
<RelativeLayout        xmlns:android="http://schemas.android.com/apk/res/android"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:background="#66000000">        …………</RelativeLayout>   
从根布局中,我们明明可以看到layout_width我们设置为了”fill_parent”,layout_height设置为了“fill_parent”;为什么非要我们在代码中还要再设置一遍:
[java] view plain copy
print?
  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
  2. mPopWindow = new PopupWindow(contentView);  
  3. mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
  4. mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);  
View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);mPopWindow = new PopupWindow(contentView);mPopWindow.setWidth(ViewGroup.LayoutParams.FILL_PARENT);mPopWindow.setHeight(ViewGroup.LayoutParams.FILL_PARENT);
带着这个疑问,我们从两个角度来分析,”源码角度”看看就好,关键的解答在第二部分:布局角度;

1、源码角度

首先,我们从源码我角度来分析为什么要设置Width和Height;我们就以setWidth()为例来追根寻底下
先看下setWidth():
[java] view plain copy
print?
  1. public void setWidth(int width) {  
  2.     mWidth = width;  
  3. }  
public void setWidth(int width) {    mWidth = width;}
然后再看看mWidth在哪里用到:
[java] view plain copy
print?
  1. private WindowManager.LayoutParams createPopupLayout(IBinder token) {  
  2.     // generates the layout parameters for the drop down  
  3.     // we want a fixed size view located at the bottom left of the anchor  
  4.     WindowManager.LayoutParams p = new WindowManager.LayoutParams();  
  5.     // these gravity settings put the view at the top left corner of the  
  6.     // screen. The view is then positioned to the appropriate location  
  7.     // by setting the x and y offsets to match the anchor’s bottom  
  8.     // left corner  
  9.     p.gravity = Gravity.LEFT | Gravity.TOP;  
  10.     p.width = mLastWidth = mWidth;  
  11.     p.height = mLastHeight = mHeight;  
  12.     if (mBackground != null) {  
  13.         p.format = mBackground.getOpacity();  
  14.     } else {  
  15.         p.format = PixelFormat.TRANSLUCENT;  
  16.     }  
  17.     p.flags = computeFlags(p.flags);  
  18.     p.type = mWindowLayoutType;  
  19.     p.token = token;  
  20.     p.softInputMode = mSoftInputMode;  
  21.     p.setTitle(”PopupWindow:” + Integer.toHexString(hashCode()));  
  22.   
  23.     return p;  
  24. }  
private WindowManager.LayoutParams createPopupLayout(IBinder token) {    // generates the layout parameters for the drop down    // we want a fixed size view located at the bottom left of the anchor    WindowManager.LayoutParams p = new WindowManager.LayoutParams();    // these gravity settings put the view at the top left corner of the    // screen. The view is then positioned to the appropriate location    // by setting the x and y offsets to match the anchor's bottom    // left corner    p.gravity = Gravity.LEFT | Gravity.TOP;    p.width = mLastWidth = mWidth;    p.height = mLastHeight = mHeight;    if (mBackground != null) {        p.format = mBackground.getOpacity();    } else {        p.format = PixelFormat.TRANSLUCENT;    }    p.flags = computeFlags(p.flags);    p.type = mWindowLayoutType;    p.token = token;    p.softInputMode = mSoftInputMode;    p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));    return p;}
上面是createPopupLayout的完整代码,我们提取一下:
[java] view plain copy
print?
  1. private WindowManager.LayoutParams createPopupLayout(IBinder token) {  
  2.     …………  
  3.     p.width = mLastWidth = mWidth;  
  4.     p.height = mLastHeight = mHeight;  
  5.     …………  
  6.     return p;  
  7. }  
private WindowManager.LayoutParams createPopupLayout(IBinder token) {    …………    p.width = mLastWidth = mWidth;    p.height = mLastHeight = mHeight;    …………    return p;}
从这里便可以清晰的看到窗体的宽度和高度都是通过mWidth和mHeight来设置的。
那问题来了,mWidth在哪里能设置呢:
通篇代码中,总共只有三个函数能设置mWidth,分别如下:
除了setWidth()函数本身,就只有PopupWindow()的两个构造函数了;
[java] view plain copy
print?
  1. public void setWidth(int width) {  
  2.     mWidth = width;  
  3. }  
  4. public PopupWindow(View contentView, int width, int height) {  
  5.     this(contentView, width, height, false);  
  6. }  
  7. public PopupWindow(View contentView, int width, int height, boolean focusable) {  
  8.    if (contentView != null) {  
  9.        mContext = contentView.getContext();  
  10.        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);  
  11.    }  
  12.    setContentView(contentView);  
  13.    setWidth(width);  
  14.    setHeight(height);  
  15.    setFocusable(focusable);  
  16. }  
public void setWidth(int width) {    mWidth = width;}public PopupWindow(View contentView, int width, int height) {    this(contentView, width, height, false);}public PopupWindow(View contentView, int width, int height, boolean focusable) {   if (contentView != null) {       mContext = contentView.getContext();       mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);   }   setContentView(contentView);   setWidth(width);   setHeight(height);   setFocusable(focusable);}
那么问题来了,如果我们没有设置width和height那结果会如何呢?
如果我们没有设置width和height,那mWidth和mHeight将会取默认值0!!!!所以当我们没有设置width和height时,并不是我们的窗体没有弹出来,而是因为他们的width和height都是0了!!!!
**那么问题又来了:Google那帮老头,不能从我们contentView的根布局中取参数吗,非要我们自己设?**
当然不是那帮老头的代码有问题,因为这牵涉了更深层次的内容:布局参数的设定问题!我们在下一部分,布局角度来解答。

2、布局角度

这部分我们着重讲一个问题:控件的布局参数从哪里来?
我们看下面这段XML:

[html] view plain copy
print?
  1. <?xml version=“1.0” encoding=“utf-8”?>  
  2. <RelativeLayout  
  3.         xmlns:android=“http://schemas.android.com/apk/res/android”  
  4.         android:layout_width=“fill_parent”  
  5.         android:layout_height=“fill_parent”>  
  6.     <LinearLayout  
  7.             android:layout_width=“match_parent”  
  8.             android:layout_height=“wrap_content”  
  9.             android:background=“@drawable/pop_bg”  
  10.             android:orientation=“vertical”  
  11.             android:paddingBottom=“2dp”  
  12.             android:layout_alignParentRight=“true”>  
  13.   
  14.         <TextView  
  15.                 android:id=“@+id/pop_computer”  
  16.                 android:layout_width=“wrap_content”  
  17.                 android:layout_height=“wrap_content”  
  18.                 style=“@style/pop_text_style”  
  19.                 android:text=“计算机”/>  
  20.     </LinearLayout>  
  21. </RelativeLayout>  
<?xml version="1.0" encoding="utf-8"?><RelativeLayout        xmlns:android="http://schemas.android.com/apk/res/android"        android:layout_width="fill_parent"        android:layout_height="fill_parent">    <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/pop_bg"            android:orientation="vertical"            android:paddingBottom="2dp"            android:layout_alignParentRight="true">        <TextView                android:id="@+id/pop_computer"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                style="@style/pop_text_style"                android:text="计算机"/>    </LinearLayout></RelativeLayout>
很明显,这段代码是个三层结构,TextView是最终的子控件。
那我现在要问了:TextView的显示大小是由谁来决定的?
是由它自己的布局layout_width=”wrap_content”、layout_height=”wrap_content”来决定的吗?
当然不是!!!!它的大小,应该是在它父控件的基础上决定的。即LinearLayout的显示大小确定了以后,才能确定TextView的大小。
这好比,如果LinearLayout的大小是全屏的,那TextView的大小就由它自己来决定了,那如果LinearLayout的大小只有一像素呢?那TextView的所显示的大小无论它自己怎么设置,最大也就显示一像素!
所以我们的结论来了:控件的大小,是建立在父控件大小确定的基础上的。
那同样:LinearLayout的大小确定是要靠RelativeLayout来决定。
那问题来了:RelativeLayout的大小靠谁决定呢?
当然是它的父控件了。
我们以前讲过ViewTree的概念,即在android中任何一个APP都会有一个根结点,然后它所有的Activity和Fragmentr所对应的布局都会加入到这个ViewTree中;在ViewTree中每一个控件是一个结点:
比如下面这个ViewTree(画的很烂……)

从上面的ViewTree中可以看到,每一个结点都是有父结点的(除了根结点,根结点不是应用的根结点,与我们应用无关),所以每一个控件都是可以找到父控件的的布局大小的。
但我们的contentView是怎么来的呢?

[java] view plain copy
print?
  1. View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);  
View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
直接inflate出来的,我们对它没有设置根结点!
那问题来了?它的大小由谁来解决呢?
好像没有谁能决定了,因为他没有父结点。那它到底是多大呢?未知!
所以只有通过代码让用户去手动设置了!所以这就是为什么非要用户设置width和height的原因了。

好了,到这里,有关PopupWIndow的东东也就讲完了,希望大家能学到东西。

如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9197073

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/49278705 谢谢


0 0
原创粉丝点击