最详细的 PopupWindow 详解

偷我乔11 8年前
   <p>我看网上对于PopupWindow的介绍非常的少就自己写一篇, 本文基本上分析了PopupWindow的所有方法.</p>    <p>PopupWindow是对于屏幕添加一个显示区域, 由于对位置和内容都非常自由所以常常在开发中用到.</p>    <p>看完后建议也看下PopupMenu详细使用</p>    <h2>创建</h2>    <p>一般用的构造方法.</p>    <pre>  <code class="language-java">PopupWindow ()    // 创建一个空的PopupWindow    PopupWindow (View contentView)        PopupWindow (int width,                   int height)    PopupWindow (View contentView,     // PopupWindow的内容View, 相当于setContentView                  int width,     // 宽, 相当于setwidth()                  int height,  // 高, 相当于setHeight                  boolean focusable) // 是否可获取焦点, 相当于setFocusable()</code></pre>    <p>通过上下文创建PopupWindow, 创建后默认有一个透明的背景.默认宽高(0,0), 没有内容和焦点的PopupWindow. 具体作用我也不知道, 估计是写自定义控件的吧. 但是PopupWindow并没有继承View.一般不使用该构造.</p>    <pre>  <code class="language-java">PopupWindow (Context context)    PopupWindow (Context context,                   AttributeSet attrs)    PopupWindow (Context context,                   AttributeSet attrs,                   int defStyleAttr)    PopupWindow (Context context,                   AttributeSet attrs,                   int defStyleAttr,                   int defStyleRes)</code></pre>    <p>创建PopuWindow必要的三个条件:</p>    <pre>  <code class="language-java">void setHeight (int height) // 因为PopupWindow没有默认布局所以必须指定宽高  void setWidth (int width)  void setContentView (View contentView) // 需要显示的内容</code></pre>    <p>缺少一个就无法显示.</p>    <p>前面提到PopupWindow需要设置宽高, 那如果想用布局中的宽高怎么办呢?</p>    <p>可以用到 LayoutParams.WRAP_CONTENT 包裹布局. 布局多大就显示多大的PopupWindow</p>    <pre>  <code class="language-java">PopupWindow popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);</code></pre>    <h2>显示</h2>    <p>显示PopupWindow可以分为两种方式:</p>    <ol>     <li>附着某个控件 showAsDropDown</li>     <li>设置屏幕坐标 showAtLocation</li>    </ol>    <h3>相对于当前控件</h3>    <p>默认是PopupWindow的左上角对其控件的左下角</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dc14177f54798bd86c49e7e727dc4758.jpg"></p>    <p>或者设置 Gravity.RIGHT , PopupWindow的右上角对齐控件的右下角</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d96abcafd6aa923e82ef8afe1fed5039.jpg"></p>    <p>不存在 Gravity.TOP 或 Gravity.BOTTOM 效果</p>    <pre>  <code class="language-java">void showAsDropDown (View anchor) // 弹窗显示在anchor控件左下方    void showAsDropDown (View anchor,                   int xoff,  // 以控件左下角为原点的偏移坐标                  int yoff)    void showAsDropDown (View anchor,                   int xoff,                   int yoff,                   int gravity)     // 弹窗显示在控件的左下方还是右下方, 参数Gravity.RIGHT/Gravity.LEFT. 默认是左下方</code></pre>    <h3>相对于当前窗口</h3>    <p>当前窗口的任意位置(不包括状态栏)</p>    <pre>  <code class="language-java">void showAtLocation (View parent, // 该属性只要是屏幕上任意控件对象即可                  int gravity, // 屏幕位置                  int x,  // 偏移坐标                  int y)</code></pre>    <p>parent:该属性只要是当前任意控件对象即可(View和ViewGroup都行), 官方文档介绍该对象参数主要是为了得到该对象的 getWindowToken() 方法.</p>    <p>需要注意的是多次调用show方法, 只会执行第一句</p>    <pre>  <code class="language-java">mPopupWindow.showAtLocation(popupwindow, Gravity.TOP, 100, 0); // 只有该行生效  mPopupWindow.showAtLocation(popupwindow, Gravity.LEFT, 100, 0);  mPopupWindow.showAtLocation(popupwindow, Gravity.RIGHT, 100, 0);  mPopupWindow.showAtLocation(popupwindow, Gravity.BOTTOM, 100, 0);</code></pre>    <h3>隐藏PopupWindow</h3>    <p>该方法只能在 show 后才能执行否则crash</p>    <pre>  <code class="language-java">void dismiss ()</code></pre>    <h2>状态</h2>    <h3>可被点击</h3>    <pre>  <code class="language-java">boolean isTouchable () // 判断是否可被点击  void setTouchable (boolean touchable) // 设置是否可被点击</code></pre>    <h3>多点触控</h3>    <pre>  <code class="language-java">void setSplitTouchEnabled (boolean enabled)  boolean isSplitTouchEnabled ()</code></pre>    <h3>忽略CheekPress事件</h3>    <p>当物体触摸在屏幕上的尺寸超过手指尺寸范围, 将被判定为CheekPress事件(脸颊点击).</p>    <pre>  <code class="language-java">void setIgnoreCheekPress () // 默认为false, 即不忽略</code></pre>    <h3>弹窗外部被点击</h3>    <p>如果为true点击PopupWindow外部区域可以取消PopupWindow</p>    <pre>  <code class="language-java">void setOutsideTouchable (boolean touchable) // 设置外部是否可被点击  boolean isOutsideTouchable ()</code></pre>    <h3>解决NavigationBar重叠</h3>    <p>这是Android5.0(API22)后添加的方法, 默认为true. 为true时将不会与导航栏重叠.</p>    <pre>  <code class="language-java">void setAttachedInDecor (boolean enabled)</code></pre>    <h3>可获取焦点</h3>    <p>一般控件都不需要焦点. 但是输入框EditText需要先获取焦点才能输入. 最重要的是当PopupWindow可获取焦点时按下手机返回键将不会销毁当前Activity而是关闭当前PopupWindow. 所以我们一般还是设置为true. 更加符合用户操作逻辑. 该方法为true时同时拥有 setOutsideTouchable(true) 的作用.</p>    <pre>  <code class="language-java">void setFocusable (boolean focusable)  boolean isFocusable ()</code></pre>    <h3>设置背景</h3>    <pre>  <code class="language-java">void setBackgroundDrawable (Drawable background)  Drawable getBackground ()</code></pre>    <h3>阴影</h3>    <p>该方法我测试无效</p>    <pre>  <code class="language-java">void setElevation (float elevation)  float getElevation ()</code></pre>    <h3>附着View位置</h3>    <p>该方法只在 showAsDropDown() 方法执行后才有效. 可以判断PopupWindow和附着View anchor谁的Y轴坐标小.</p>    <pre>  <code class="language-java">boolean isAboveAnchor ()</code></pre>    <h3>遮盖附着View</h3>    <pre>  <code class="language-java">void setOverlapAnchor (boolean overlapAnchor)  boolean getOverlapAnchor ()</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8e3b40a2a9f9b93298e86756e165b8ae.jpg"></p>    <p>可以从图中看到对齐方式从View anchor的左下角变成了左上角了.</p>    <h3>设置PopupWindow宽高</h3>    <p>该方法在API23后被废弃, 由setWidth(int) 和 setHeight(int)替代</p>    <pre>  <code class="language-java">void setWindowLayoutMode (int widthSpec,                   int heightSpec)</code></pre>    <h3>窗口裁剪</h3>    <p>PopupWindow默认是不会超出屏幕边界的. 但是如果该方法为false时会采用精准位置, 能超出屏幕范围.</p>    <pre>  <code class="language-java">void setClippingEnabled (boolean enabled)  boolean isClippingEnabled ()</code></pre>    <p>演示超出屏幕:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1d196783aba38d58980448829601ea2a.jpg"></p>    <pre>  <code class="language-java">mPopupWindow.showAtLocation(mBtnOpenPopup, Gravity.BOTTOM, 0, -30);</code></pre>    <h2>动画效果</h2>    <h3>设置动画</h3>    <p>可以设置popupWindow的显示和隐藏动画</p>    <pre>  <code class="language-java">void setAnimationStyle (int animationStyle)    int getAnimationStyle ()</code></pre>    <p>可以看到方法是传入一个Style的样式id</p>    <p>示例:</p>    <pre>  <code class="language-java"><style name="popupwindow_anim_style">      <item name="android:windowEnterAnimation">@anim/dialog_bottom_enter</item>      <item name="android:windowExitAnimation">@anim/dialog_bottom_exit</item>    </style></code></pre>    <p>分别由两个属性组成. 两个属性各代表一个anim动画文件.</p>    <h3>进入和退出动画</h3>    <p>这是在Android6.0(API 23)后加入的方法. 配合Material Design的转场动画使用.</p>    <p>进入动画</p>    <pre>  <code class="language-java">void setEnterTransition (Transition enterTransition)  Transition getEnterTransition ()</code></pre>    <p>退出动画</p>    <pre>  <code class="language-java">void setExitTransition (Transition exitTransition)  Transition getExitTransition ()</code></pre>    <h2>获取</h2>    <h3>获取最大高度</h3>    <p>这是相当于传入的View对象可显示的最大高度. 即PopupWindow使用 showAsDropDown() 能够显示的最大高度</p>    <pre>  <code class="language-java">int getMaxAvailableHeight (View anchor)    int getMaxAvailableHeight (View anchor,                   int yOffset) // 控件Y轴偏移后可显示的最大高度    int getMaxAvailableHeight (View anchor,  // api24增加的方法, 由于我手上没有7.0设备就不说了.                  int yOffset,                   boolean ignoreBottomDecorations)</code></pre>    <h2>输入模式</h2>    <p>针对PopupWindow中包含EditText控件.</p>    <h3>输入模式</h3>    <p>我使用该方法的三种模式我感觉并没有什么卵用</p>    <pre>  <code class="language-java">void setInputMethodMode (int mode)  int getInputMethodMode ()</code></pre>    <p>支持三种模式</p>    <ol>     <li>INPUT_METHOD_FROM_FOCUSABLE 根据可否获取焦点判断是否可输入. 感觉鸡肋</li>     <li>INPUT_METHOD_NEEDED 允许输入</li>     <li>INPUT_METHOD_NOT_NEEDED 不允许输入</li>    </ol>    <h3>软键盘模式</h3>    <pre>  <code class="language-java">void setSoftInputMode (int mode) // mode为WindowManager.LayoutParams的softInputMode常量  int getSoftInputMode ()</code></pre>    <p>softInputMode</p>    <p>包含九种取值, 可组合使用,分为两类:</p>    <p>显示状态模式</p>    <ol>     <li>SOFT_INPUT_STATE_UNSPECIFIED 默认模式</li>     <li>SOFT_INPUT_STATE_HIDDEN</li>     <li>SOFT_INPUT_STATE_ALWAYS_HIDDEN 总是隐藏</li>     <li>SOFT_INPUT_STATE_UNCHANGED</li>     <li>SOFT_INPUT_STATE_VISIBLE</li>     <li>SOFT_INPUT_STATE_ALWAYS_VISIBLE 自动弹出软键盘</li>    </ol>    <p>调整模式</p>    <ol>     <li>SOFT_INPUT_ADJUST_UNSPECIFIED 默认模式</li>     <li>SOFT_INPUT_ADJUST_RESIZE 软键盘弹出后PopupWindow会自动调整坐标,不被遮挡</li>     <li>SOFT_INPUT_ADJUST_PAN</li>    </ol>    <h2>监听事件</h2>    <h3>隐藏事件监听</h3>    <p>即PopupWindow执行 dismiss() 后回调的方法.</p>    <pre>  <code class="language-java">void setOnDismissListener (PopupWindow.OnDismissListener onDismissListener)</code></pre>    <h3>触摸事件拦截</h3>    <pre>  <code class="language-java">void setTouchInterceptor (View.OnTouchListener l)</code></pre>    <h2>更新</h2>    <p>以下的更新PopupWindow都必须在PopupWindow处于以及被显示的状态下才行. 且PopupWindow的宽高设置都必须大于等于0. 如果想忽略PopupWindow的宽高设置就设为-1.</p>    <h3>更新状态</h3>    <p>该方法不能更新PopupWindow的宽高, 只能更新PopupWindow的状态. 例如更新 Focusable 和 OutsideTouchable</p>    <pre>  <code class="language-java">void update ()</code></pre>    <h3>更新尺寸</h3>    <p>上面说过 update() 不能更新PopupWindow的宽高, 但是提供更新宽高的update方法</p>    <pre>  <code class="language-java">void update (int width, // 更新PopupWindow的宽高                  int height)</code></pre>    <h3>更新显示位置</h3>    <p>该方法是相当于重新 showAsDropDown , 所以这是相对于控件的位置更新</p>    <pre>  <code class="language-java">void update (View anchor, // 更新显示控件的位置                  int width,                   int height)    void update (View anchor,                   int xoff, // 相对于控件的偏移值                  int yoff,                   int width,                   int height)</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a9c700bd78dde279db0548fad5ec7196.gif"></p>    <h3>相对位置更新</h3>    <p>是相对于当前的位置进行偏移. 不同的显示位置对于的相对原点也不同.</p>    <p>showAsDropDown 的相对原点是整个屏幕左上角, 包括状态栏. 所以由于包括状态栏所以坐标偏移的时候一定要y轴偏移大于60超出状态栏的高度. 否则因为遮挡状态栏导致PopupWindow无法显示.</p>    <pre>  <code class="language-java">mPopupWindow.update(50, 60, -1,-1); // x轴偏移50</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bf4c5762e16b8ae427f5c8191edb80ff.jpg"></p>    <p>showAtLocation 的相对原点是自身位置.</p>    <pre>  <code class="language-java">void update (int x, // 坐标偏移                  int y,                   int width, // PopupWindow宽高                  int height)    void update (int x,                   int y,                   int width,                   int height,                   boolean force) // 可获取焦点</code></pre>    <p> </p>    <p>来自:https://juejin.im/post/58ed82c3a22b9d0063469e98</p>    <p> </p>