前言

最近在项目中用到了 PopUpWindow,并且在机型适配时发现华为等具有虚拟按键的手机在横屏状态时会造成 PopUpWindow 显示位置偏移的情况存在,最后完美解决了这个问题,所以把经验分享出来,看能否对你有用。

弹窗有很多种实现方式,例如:

  1. Dialog
  2. DialogFragment
  3. Fragment
  4. PopUpWindow
  5. ListPopUpWindow

在这些方式中,我们主要讲 PopUpWindow 的使用方式,因为它能在任意位置弹出, 这是其他方式很难做到的。

PopUpWindow 是什么

从 Google 爸爸的开发文档中我们不难看出,首先它是一个 Window,弹出时位于其他控件的上层。

 
image.png

怎么使用 PopUpWindow

  • 创建布局文件
  • 创建 ContentView
  • 设置 PopUpWindow
  • Show

创建布局文件

PopUpWindow 就是一个容器,是需要编写对应的布局文件,在项目需求中我们要做成这样的效果

 
image.png

<com.tutu.app.view.TutuRegisterTitleView xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:background="@color/sdk_background"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tutu_game_register_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_marginTop="4dp"
        android:textAlignment="center"
        android:layout_marginBottom="6dp"
        android:textColor="#333333"
        android:textSize="17dp" />

    <View
        style="@style/TutuGameFullLandscapeLine"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"/>
</com.tutu.app.view.TutuRegisterTitleView>

创建 ContentView

ContentView 是我们将布局文件生成的View,并且将其放入 PopUpWindow 中。


        // 初始化popUpWindow
        linearLayout = new LinearLayout(getContext());
        // 生成 View 对象
        popRootView =  View.inflate(getContext(), 
        // PopUpWindow 传入 ContentView
        popupWindow = new PopupWindow(popRootView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

设置 PopUpWindow

看到这里,有人可能问:我们在布局文件中已经设置了背景颜色,为什么要在Java代码中重复设置呢?

因为在某些 Andorid 系统版本中会出现点击外部和返回键弹窗却无法消失的 Bug,所以你懂的。

附上某大牛对该 Bug 的分析 PopupWindow 点击外部和返回键无法消失背后的真相

        // 设置背景
        popupWindow.setBackgroundDrawable(new ColorDrawable());
        // 外部点击事件
        popupWindow.setOutsideTouchable(true);

Show

这个就很简单,短短一行代码,控制弹窗的显示实际位置,难点在于 x,y 值的确定

            // 传入 AnchorView ,锚点实际为 Window
            // Gravity.TOP 在该锚点的正上方
            // Gravity.LEFT 在屏幕左侧
            // Gravity.NO_GRAVITY,在屏幕左上角
            // x 为坐标系 x轴方向的偏移量,左负右正
            // y 为坐标系 y轴方向的偏移量,上负下正
            
            popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
      
          
            popupWindow.showAtLocation(view, Gravity.NO_GRAVITY,x, y);

      
            popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
           

怎样使用 ListPopUpWindow

 
GS20180625140257.gif

和PopUpWindow 相比,它更适合展示多条数据,内部包含了一个 ListView ,那就意味着需要 Adapter 进行数据的绑定。

 listPopupWindow = new ListPopupWindow(getContext());
            // 适配器添加数据
            listPopupWindowAdapter.addAdapterList(list);
            // 添加适配器
            listPopupWindow.setAdapter(listPopupWindowAdapter);
            // 设置弹窗的大小和位置
            listPopupWindow.setWidth(width+horizontalOffset*2);
            listPopupWindow.setHeight(height);
            listPopupWindow.setAnchorView(view);
            listPopupWindow.setModal(true);
            listPopupWindow.setVerticalOffset(-VerticalOffset);
            listPopupWindow.setHorizontalOffset(-horizontalOffset*4);
            // 设置背景
            listPopupWindow.setBackgroundDrawable(
                    BitmapUtils.getDrawableFromResource(getContext(), RUtils.drawable(getContext(),"tutu_area_code_background")));

虚拟按键对 PopUpWindow 的影响

虚拟按键的机型在横屏状态下,会造成一个 x 轴方向的偏移(根据具体代码确定),所以我们使用神器 getLocationInWindow,获取锚点 View 在当前 Window 的坐标,然后通过计算确定弹窗出现位置。

具体决定方案,请看 Fucking Code

  // 获取锚点 View 在屏幕中的坐标
  int[] location = new int[2];
                    back.getLocationInWindow(location);
                    
                    int x = location[0];//获取当前位置的横坐标
                    int y = location[1];//获取当前位置的纵坐标

 // 竖屏不做处理
        if (VERTICAL_SCREEN == getContext().getResources().getConfiguration().orientation){
            popupWindow.showAtLocation(view, Gravity.TOP, 0, y);            
        }
        // 横屏状态
        else if (HORIZONTALL_SCREEN == getContext().getResources().getConfiguration().orientation) {
            // 检测是否有虚拟按键
            if (checkDeviceHasNavigationBar(getContext())){

                  popupWindow.showAtLocation(view, Gravity.NO_GRAVITY,x, y);

            }else {
                popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
            }
        }
 

作者:毛先森
链接:https://www.jianshu.com/p/087ab88c5b7c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。