首页 > 代码库 > PopupWindow使用

PopupWindow使用

一.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.setBackgroundDrawable(
mBackground);
            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);
     pw.showAsDropDown(v);
     效果:PopupWindow不会获得焦点,里面的任何控件均得不到响应。区域外面的响应正常。

2.设置focusable属性
     View view = getLayoutInflater().inflate(R.layout.popnull);
     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.popnull);
     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.popnull);
     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;

     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;
     }


PopupWindow使用