刘海屏下悬浮球的位置计算

背景

为了不影响到用户玩游戏的体验,渠道菜单通常以悬浮小球的方式显示在游戏界面中,支持拖动和隐藏。点击之后弹出对应的菜单然后跳转至功能页面。

实现方案

通过设置onTouchListener来监听手指的滑动情况,在ACTION_MOVE中实时获取小球在屏幕中的位置,减去点击事件相对小球的位置,通过更新layoutParams来动态改变悬浮球的位置
具体代码如下:

		@Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:

                    /*
                     *  手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                     *  全屏状态考虑状态栏高度
                     */
                    xInView = event.getX();
                    yInView = event.getY();
                    xInScreen = event.getRawX();
                    yInScreen = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    yInScreen = event.getRawY();
                    xInScreen = event.getRawX();
                        
                    // 手指移动的时候更新小悬浮窗的位置
                    updateViewPosition(false);
                    break;
                case MotionEvent.ACTION_UP:
                    ...
                    break;
                default:
                    break;
            }
            return true;
        }
 // 更新悬浮球的位置
 public void updateViewPosition(boolean isUp) {
        floatViewParams.x = (int) (xInScreen - xInView);
        floatViewParams.y = (int) (yInScreen - yInView);
        ...
}

用上面这种方法来实现在刘海屏后面发生了一个异常,具体表现为:在刘海屏幕手机上面,移动小球时候,小球的位置会相对正确的位置有一个向下的偏移。
经过分析,发现通过event.getRawY()获取的位置是相对屏幕的绝对位置,也就是屏幕的上边缘;但是在绘制的时候却避开了刘海,导致view的真实绘制位置向下移动了一个刘海的高度。
这里转载一张图说明一下:
刘海屏下悬浮球的位置计算_第1张图片

解决思路

如果能判断出设备是否有刘海屏,然后知道其高度,通过event.getRawY()获取到高度之后减去刘海的高度就能解决问题。

1、判断设备是否有刘海

刘海屏是在Android P里面推出的新功能,但是很多Android O的手机也实现了刘海,那么获取刘海信息就分9.0之前和9.0之后。
9.0之前:各家设备厂商实现方案不一样,判断方式也不一样,以下代码是主流手机厂商判断方法:

/**
     * 判断是否是刘海屏
     * @return
     */
    public static boolean hasNotchScreen(Activity activity){
        if (getInt("ro.miui.notch",activity) == 1 || hasNotchAtHuawei(activity) || hasNotchAtOPPO(activity)
                || hasNotchAtVivo(activity)){
            return true;
        }

        return false;
    }
    
    /**
     * 小米刘海屏判断.
     * @return 0 if it is not notch ; return 1 means notch
     * @throws IllegalArgumentException if the key exceeds 32 characters
     */
    public static int getInt(String key,Activity activity) {
        int result = 0;
        if (isXiaomi()){
            try {
                ClassLoader classLoader = activity.getClassLoader();
                @SuppressWarnings("rawtypes")
                Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
                //参数类型
                @SuppressWarnings("rawtypes")
                Class[] paramTypes = new Class[2];
                paramTypes[0] = String.class;
                paramTypes[1] = int.class;
                Method getInt = SystemProperties.getMethod("getInt", paramTypes);
                //参数
                Object[] params = new Object[2];
                params[0] = new String(key);
                params[1] = new Integer(0);
                result = (Integer) getInt.invoke(SystemProperties, params);

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 华为刘海屏判断
     * @return
     */
    public static boolean hasNotchAtHuawei(Context context) {
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
            ret = (boolean) get.invoke(HwNotchSizeUtil);
        } catch (ClassNotFoundException e) {
            Log.e("","hasNotchAtHuawei ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("","hasNotchAtHuawei NoSuchMethodException");
        } catch (Exception e) {
            Log.e("","hasNotchAtHuawei Exception");
        } finally {
            return ret;
        }
    }

    public static final int VIVO_NOTCH = 0x00000020;
    public static final int VIVO_FILLET = 0x00000008;

    /**
     * VIVO刘海屏判断
     * @return
     */
    public static boolean hasNotchAtVivo(Context context) {
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class FtFeature = classLoader.loadClass("android.util.FtFeature");
            Method method = FtFeature.getMethod("isFeatureSupport", int.class);
            ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
        } catch (ClassNotFoundException e) {
            Log.e("","hasNotchAtVivo ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("","hasNotchAtVivo NoSuchMethodException");
        } catch (Exception e) {
            Log.e("","hasNotchAtVivo Exception");
        } finally {
            return ret;
        }
    }
    /**
     * OPPO刘海屏判断
     * @return
     */
    public static boolean hasNotchAtOPPO(Context context) {
        return  context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }


    // 是否是小米手机
    public static boolean isXiaomi() {
        return "Xiaomi".equals(Build.MANUFACTURER);
    }

9.0之后:
通过获取DisplayCutout来判断刘海的位置和大小。

/**
     * Android P 刘海屏判断
     * @param activity
     * @return
     */
    public static DisplayCutout isAndroidP(Activity activity){
        View decorView = activity.getWindow().getDecorView();
        if (decorView != null && android.os.Build.VERSION.SDK_INT >= 28){
            WindowInsets windowInsets = decorView.getRootWindowInsets();
            if (windowInsets != null) {
                return windowInsets.getDisplayCutout();
            }
        }
        return null;
    }

2、计算刘海的高度

大部分刘海屏的高度和状态栏的高度一致,所以读取状态栏的高度就等同于刘海的高度,但是也有一些不一致。这样就存在误差。
获取状态栏高度

int resourceId = mContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
    cutStatusHeight = mContext.getResources().getDimensionPixelSize(resourceId);
}

如果是9.0,就直接通过DisplayCutout来获取刘海的高度即可。

3、横竖屏处理

在判断出设备是否有刘海屏并得到其高度之后,在计算悬浮球位置的时候减去其高度,就能得到正确的坐标。
经过自测成功解决问题,美滋滋。
经过一段时间,测试找上门来,发现悬浮球还能斜着偏移,惊呆了。
一看游戏是横屏的,XY坐标竖屏状态相反,本来应该在X方向上面减结果在Y方向上面减,导致其位置往斜方向偏移。在计算的时候判断一下界面的横竖屏状态即可解决问题。

你可能感兴趣的:(Android,移动开发,刘海屏)