Android PopupWindow/悬浮窗适配刘海屏

一、悬浮窗适配刘海屏

悬浮窗正常情况下,是不能移动到刘海屏所在的区域,那么你看到结果就是:类似这样
Android PopupWindow/悬浮窗适配刘海屏_第1张图片
如上图所示,刚好是隔了一个刘海屏宽度的距离
要解决悬浮窗可以靠边显示到刘海屏所在区域,只要设置一下Flag那个参数为LayoutParams.FLAG_LAYOUT_NO_LIMITS即可,主要如下:

mParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS;

这个参数指明悬浮窗的位置不受系统屏幕边界的限制,也就是可以移出屏幕,所以在悬浮窗的onTouch事件中要注意控制悬浮窗不要被移出了屏幕:

 case MotionEvent.ACTION_MOVE: {
        mParams.x += (int) event.getRawX() - x;
        mParams.y += (int) event.getRawY() - y;

        //限制不要移出屏幕
        mParams.x = mParams.x < 0 ? 0 : mParams.x;
        mParams.x = mParams.x > mSreenWidth - mParams.width ? mSreenWidth - mParams.width : mParams.x;

        mParams.y = mParams.y < 0 ? 0 : mParams.y;
        mParams.y = mParams.y > mScreenHeight - mParams.height ? mScreenHeight - mParams.height : mParams.y;

        mWindowManager.updateViewLayout(mContentView, mParams);
        x = (int) event.getRawX();
        y = (int) event.getRawY();
        break;
   }

其中,用到的屏幕宽高,最好是用当前窗口可见区域的大小来表示,避免刘海屏影响悬浮窗的位置

 //获取当前窗口可见区域宽高
Rect rect = new Rect();
act.getWindow().getDecorView().getGlobalVisibleRect(rect);
mSreenWidth = rect.right - rect.left;
mScreenHeight = rect.bottom - rect.top;

二、PopupWindow适配刘海屏

PopupWindow按正常流程设置如下:

            mPopupWindow = new CustomPopupWindow(contentView, width, height, false);

            mPopupWindow.setOnBackPressListener(new CustomPopupWindow.OnBackPressListener() {
                @Override
                public void onBack() {
                    wv.loadUrl("javascript:android_back()");
                }
            });


            if (isLandscape) {//横屏
                mPopupWindow.setAnimationStyle(ResourceUtil.getStyleId(mActivityRef.get(), "com_sswl_left_anim"));
            } else {

                mPopupWindow.setAnimationStyle(ResourceUtil.getStyleId(mActivityRef.get(), "com_sswl_bottom_anim"));

            }

      
            //在指定位置显示出来
            mPopupWindow.showAtLocation(mActivityRef.get().getWindow().getDecorView(), Gravity.TOP | Gravity.LEFT, x, y);

然而,即使x =0,y=0,PopupWindow也并没有靠边显示,明显是隔了一个刘海的区域
Android PopupWindow/悬浮窗适配刘海屏_第2张图片
这时候需要设置一下PopupWindow内容不受屏幕限制修剪,即设置:

   //设置这个,popuwindow才能显示到刘海屏区域
  mPopupWindow.setClippingEnabled(false);

这样设置之后,假如你的contentview宽高是具体尺寸是没有什么问题了,但是假如是MATCH_PARENT的话,里面的内容就会显示得很大,超出可视区域,所以,要是contentview宽高是MATCH_PARENT,还是需要设置具体的尺寸给它

  // setClippingEnabled(false)之后,一定要设置里面的宽高,要不里面内容会变得很大
 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(width, height);
 llWv.setLayoutParams(lp);

完成上述步骤,就完美解决了PopupWindow不能靠边显示问题了

【扩展延伸】

1、上面提到PopupWindow怎么适配刘海屏,但是可能很多小伙伴还不知道怎么设置自己的Activity,才使得Activity的内容延伸到刘海屏区域,设置其实也很简单,从Android 9.0开始,Google提供了官方Api可以设置,直接在你的Activity onCreate方法中setContentView之前调用即可

  //解决android 9.0水滴屏/刘海屏有黑边的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
       WindowManager.LayoutParams lp = getWindow().getAttributes();
       lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
       getWindow().setAttributes(lp);
}

其中,lp.layoutInDisplayCutoutMode可以有三种取值,不同取值的效果说明如下:

模式 模式说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 该窗口决不允许与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。

经过上述设置之后Activity的内容是显示到了刘海区域,但是弹出的Dialog的时候,刘海区域的黑边又出来了,所以也需要在Dialog的onCreate方法中设置上述代码,因为上述代码是对每个window单独设置生效的,弹出的dialog属于新的window,故需要重新设置
2、经过上述设置,activity的内容是延伸到了刘海屏,但是这时候又引入了一个新的问题,那就是你的activity内容延伸到刘海区域显示了,这就存在被刘海遮挡的问题,所以就需要我们计算出刘海的位置与宽高,在Android 9.0之后Google官方提供了Api可以获取:

  @TargetApi(28)
  public void getNotchParams() {
        final View decorView = getWindow().getDecorView();
 
        decorView.post(new Runnable() {
            @Override
            public void run() {
                DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout();
                Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
                Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.getSafeInsetRight());
                Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.getSafeInsetTop());
                Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
                
                List<Rect> rects = displayCutout.getBoundingRects();
                if (rects == null || rects.size() == 0) {
                    Log.e("TAG", "不是刘海屏");
                } else {
                    Log.e("TAG", "刘海屏数量:" + rects.size());
                    for (Rect rect : rects) {
                        Log.e("TAG", "刘海屏区域:" + rect);
                    }
                }
            }
        });
    }

输出结果,看一下:

E/TAG: 安全区域距离屏幕左边的距离 SafeInsetLeft:81
E/TAG: 安全区域距离屏幕右部的距离 SafeInsetRight:0
E/TAG: 安全区域距离屏幕顶部的距离 SafeInsetTop:0
E/TAG: 安全区域距离屏幕底部的距离 SafeInsetBottom:0
E/TAG: 刘海屏数量:1
E/TAG: 刘海屏区域:Rect(0, 428 - 81, 653)

这个是横屏Activity,所以你可以看到日志,假如你Activity内容距离左边为81px的话,是不会被刘海遮挡的,而且刘海占据的区域是横向x坐标:0到81,竖向y坐标:428到653

3、上面提到的解决方案都是Android 9.0之后的,但是在Android 9.0之前就有很多国产机子出现了刘海屏/水滴屏,那么在没有官方提供Api,我们就需要单独对每个牌子的手机进行适配,这些可以在对应牌子的官网中找
1)先通过每个牌子提供的Api判断是否是有刘海屏的机子:

public class DisplayCutoutUtils {
    /**
     * 是否是刘海屏手机
     * @param activity
     * @return
     */
    public static boolean isCutoutScreen(Activity activity) {
        try {
            if (activity == null) {
                return false;
            }
            if (Build.VERSION.SDK_INT >= 28) {
                return activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout() != null;
            }
            String manufacturer = Build.MANUFACTURER;
            if (!TextUtils.isEmpty(manufacturer)) {
                if (manufacturer.equalsIgnoreCase("HUAWEI")) {
                    return hwCutout(activity);
                }
                if (manufacturer.equalsIgnoreCase("xiaomi")) {
                    return xiaomiCutout(activity);
                }
                if (manufacturer.equalsIgnoreCase("oppo")) {
                    return oppoCutout(activity);
                }
                if (manufacturer.equalsIgnoreCase("vivo")) {
                    return vivoCutout(activity);
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
            Logger.e(e.getMessage());
        }
        return false;
    }

    /**
     * 获取状态栏高度
     * @param activity
     * @return
     */
    public static int getStatusBarHeight(Activity activity) {
        int identifier;
        if (activity != null && (identifier = activity.getResources().getIdentifier("status_bar_height", "dimen", "android")) > 0) {
            return activity.getResources().getDimensionPixelSize(identifier);
        }
        return 0;
    }


    /**
     * 华为刘海屏判断
     * @param activity
     * @return
     */
    public static boolean hwCutout(Activity activity) {
        try {
            Class<?> loadClass = activity.getClassLoader().loadClass("com.huawei.android.util.HwNotchSizeUtil");
            return ((Boolean) loadClass.getMethod("hasNotchInScreen", new Class[0]).invoke(loadClass, new Object[0])).booleanValue();
        } catch (Exception unused) {
            return false;
        }
    }


    /**
     * oppo刘海屏判断
     * @param activity
     * @return
     */
    public static boolean oppoCutout(Activity activity) {
        return activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }


    /**
     * vivo刘海屏判断
     * @param activity
     * @return
     */
    public static boolean vivoCutout(Activity activity) {
        try {
            Class<?> cls = Class.forName("android.util.FtFeature");
            return ((Boolean) cls.getMethod("isFeatureSupport", new Class[]{Integer.TYPE}).invoke(cls, new Object[]{32})).booleanValue();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 小米刘海屏判断
     * @param activity
     * @return
     */
    public static boolean xiaomiCutout(Activity activity) {
        try {
            Class<?> cls = Class.forName("android.os.SystemProperties");
            return ((Integer) cls.getMethod("getInt", new Class[]{String.class, Integer.TYPE}).invoke(cls, new Object[]{"ro.miui.notch", 0})).intValue() == 1;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}

2)再获取刘海区域的尺寸,这个基本是获取状态栏的高度就差不多了,刘海屏一般是在状态栏高度内或者略大于状态栏高度而已


    public static int getStatusBarHeight(Context context) {
        int statusBarHeight = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
        }
        return statusBarHeight;
    }

你可能感兴趣的:(android,java,游戏,android)