记录安卓手机 刘海屏判断及获取信息

object NotchSupportUtil {

    //----------------------huawei
    fun hasNotchAtHuawei(context: Context): Boolean {
        var ret = false
        try {
            val classLoader = context.getClassLoader()
            val HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil")
            val get = HwNotchSizeUtil.getMethod("hasNotchInScreen")
            ret = get.invoke(HwNotchSizeUtil) as Boolean
        } catch (e: ClassNotFoundException) {
            Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException")
        } catch (e: NoSuchMethodException) {
            Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException")
        } catch (e: Exception) {
            Log.e("Notch", "hasNotchAtHuawei Exception")
        } finally {
            return ret
        }
    }

    //获取刘海尺寸:width、height
    //int[0]值为刘海宽度 int[1]值为刘海高度
    fun getNotchSizeAtHuawei(context: Context): IntArray {
        var ret = intArrayOf(0, 0)
        try {
            val cl = context.classLoader
            val HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil")
            val get = HwNotchSizeUtil.getMethod("getNotchSize")
            ret = get.invoke(HwNotchSizeUtil) as IntArray
        } catch (e: ClassNotFoundException) {
            Log.e("Notch", "getNotchSizeAtHuawei ClassNotFoundException")
        } catch (e: NoSuchMethodException) {
            Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException")
        } catch (e: Exception) {
            Log.e("Notch", "getNotchSizeAtHuawei Exception")
        } finally {
            return ret
        }
    }

    //------------------------vivo
    val VIVO_NOTCH = 0x00000020//是否有刘海
    val VIVO_FILLET = 0x00000008//是否有圆角
//    vivo不提供接口获取刘海尺寸,目前vivo的刘海宽为100dp,高为27dp。

    fun hasNotchAtVivo(context: Context): Boolean {
        var ret = false
        try {
            val classLoader = context.classLoader
            val FtFeature = classLoader.loadClass("android.util.FtFeature")
            val method = FtFeature.getMethod("isFeatureSupport", Int::class.javaPrimitiveType)
            ret = method.invoke(FtFeature, VIVO_NOTCH) as Boolean
        } catch (e: ClassNotFoundException) {
            Log.e("Notch", "hasNotchAtVivo ClassNotFoundException")
        } catch (e: NoSuchMethodException) {
            Log.e("Notch", "hasNotchAtVivo NoSuchMethodException")
        } catch (e: Exception) {
            Log.e("Notch", "hasNotchAtVivo Exception")
        } finally {
            return ret
        }
    }
//------------------------oppo
    /**
     * OPPO不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。不排除以后机型会有变化。
    其显示屏宽度为1080px,高度为2280px。刘海区域则都是宽度为324px, 高度为80px。
     */
    fun hasNotchAtOPPO(context: Context): Boolean {
        return context.packageManager.hasSystemFeature("com.oppo.feature.screen.heteromorphism")
    }


    /**小米手机获取刘海高度
     * 其他手机也可以通过这个方法来间接避开刘海屏,但是有可能有些手机的刘海屏高度会高于状态栏的高度,所以这个方法获取到的结果并不一定安全。
     */
    fun getStatusBarHeight(context: Context): Int {
        var statusBarHeight = 0
        val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
        if (resourceId > 0) {
            statusBarHeight = context.resources.getDimensionPixelSize(resourceId)
        }
        return statusBarHeight
    }

    /**
     * 是否是刘海屏幕
     * true fasle
     */
    fun hasNotch(context: Context): Boolean {
        val maunfacturer = Build.MANUFACTURER
        when (maunfacturer) {
            "XIAOMI", "xiaomi" -> return hasNotchAtXiaomi(context)
            "HUAWEI", "huawei" -> return hasNotchAtHuawei(context)
            "VIVO", "vivo" -> return hasNotchAtVivo(context)
            "OPPO" -> return hasNotchAtOPPO(context)
            else -> return isAndroidP(context.getTopActivity())
        }
        return false
    }


    /**
     * Android P 刘海屏判断
     * @param activity
     * @return
     */
    fun isAndroidP(context: Activity): Boolean {
        val decorView = context.window.decorView
        if (decorView != null && Build.VERSION.SDK_INT >= 28) {
            val windowInsets = decorView.rootWindowInsets
            if (windowInsets != null) {
                val disCutoutCount = windowInsets.displayCutout
                return null != disCutoutCount
            }
        }
        return false
    }


    /**
     * 小米刘海屏判断.
     * @return 0 if it is not notch ; return 1 means notch
     * @throws IllegalArgumentException if the key exceeds 32 characters
     */
    //--------------小米
    fun hasNotchAtXiaomi(context: Context): Boolean {
        var ret = false
        var result: Int
        try {
            val classLoader = context.classLoader
            val SystemProperties = classLoader.loadClass("android.os.SystemProperties")
            val paramTypes = arrayOfNulls<Class<*>>(2)
            paramTypes[0] = String::class.javaPrimitiveType
            paramTypes[1] = Int::class.javaPrimitiveType
            val getInt = SystemProperties.getMethod("getInt", *paramTypes)
            val params = arrayOfNulls<Any>(2)
            params[0] = "ro.miui.notch"
            params[1] = 0
            result = getInt.invoke(SystemProperties, params) as Int
            return 1 == result

        } catch (e: ClassNotFoundException) {
            Log.e("Notch", "hasNotchAtVivo ClassNotFoundException")
        } catch (e: NoSuchMethodException) {
            Log.e("Notch", "hasNotchAtVivo NoSuchMethodException")
        } catch (e: Exception) {
            Log.e("Notch", "hasNotchAtVivo Exception")
        } finally {
            return ret
        }

        return false
    }


    /**
     * 获取刘海屏幕的信息
     */
    fun getNotchParams(activity: Activity) {
        if(Build.VERSION.SDK_INT >= 28){
            val decorView2 = activity.window.decorView
            decorView2.post {
                val displayCutout = decorView2.rootWindowInsets.displayCutout
                Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.safeInsetLeft)
                Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.safeInsetRight)
                Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.safeInsetTop)
                Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.safeInsetBottom)

                val rects = displayCutout.boundingRects
                if (rects == null || rects.size == 0) {
                    Log.e("TAG", "不是刘海屏")
                } else {
                    Log.e("TAG", "刘海屏数量:" + rects.size)
                    for (rect in rects) {
                        Log.e("TAG", "刘海屏区域:$rect")
                    }
                }
            }
        }
    }


}
优化ResizeDraggable和BaseDraggable类,希望宽高只可以等比例同时变动。 package com.maiyou.maiysdk.util.window.draggable; import android.view.MotionEvent; import android.view.View; import com.maiyou.maiysdk.util.DisplayUtil; import com.maiyou.maiysdk.util.ResourceUtil; import com.maiyou.maiysdk.util.window.EasyWindow; public class ResizeDraggable extends BaseDraggable { private static int MIN_SIZE = 0; // 最小尺寸(px) private static int MAX_SIZE = 0; // 最大尺寸(px) private int mInitialWidth; private int mInitialHeight; private int mInitialX; private int mInitialY; private int mFixedCornerX; // 固定角点X坐标 private int mFixedCornerY; // 固定角点Y坐标 private boolean isResizing = false; private int mCornerType; // 当前操作的角落类型 // 角落类型常量 private static final int CORNER_BOTTOM_LEFT = 3; private static final int CORNER_BOTTOM_RIGHT = 4; private static final int CORNER_LEFT = 5; // 左边框 private static final int CORNER_RIGHT = 6; // 右边框 @Override public boolean isTouchMoving() { return isResizing; } @Override public void start(EasyWindow<?> easyWindow) { super.start(easyWindow); // 移除宽高比约束,允许自由调整 MIN_SIZE = DisplayUtil.dip2px(easyWindow.getContext(), 150); MAX_SIZE = DisplayUtil.dip2px(easyWindow.getContext(), 300); } @Override public boolean onTouch(View v, MotionEvent event) { int id = v.getId(); int drag_left = ResourceUtil.getId(v.getContext(), "drag_left"); int drag_right = ResourceUtil.getId(v.getContext(), "drag_right"); int drag_bottom_left = ResourceUtil.getId(v.getContext(), "drag_bottom_left"); int drag_bottom_right = ResourceUtil.getId(v.getContext(), "drag_bottom_right"); if (id == drag_left || id == drag_right || id == drag_bottom_left || id == drag_bottom_right) { // 包含顶部边框 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mInitialWidth = getViewWidth(); mInitialHeight = getViewHeight(); mInitialX = getViewOnScreenX(); mInitialY = getViewOnScreenY(); isResizing = true; // 确定操作角落类型 if (id == drag_left || id == drag_bottom_left) { mCornerType = CORNER_BOTTOM_LEFT; mFixedCornerX = mInitialX + mInitialWidth; mFixedCornerY = mInitialY; } else if (id == drag_right || id == drag_bottom_right) { mCornerType = CORNER_BOTTOM_RIGHT; mFixedCornerX = mInitialX; mFixedCornerY = mInitialY; } break; case MotionEvent.ACTION_MOVE: float currentX = event.getRawX(); float currentY = event.getRawY(); int newWidth = mInitialWidth; int newHeight = mInitialHeight; int newX = mInitialX; int newY = mInitialY; // 根据角落类型计算新尺寸和位置 switch (mCornerType) { case CORNER_LEFT: // 左边框 newWidth = (int) (mFixedCornerX - currentX); newX = (int) currentX; break; case CORNER_RIGHT: // 右边框 newWidth = (int) (currentX - mFixedCornerX); break; case CORNER_BOTTOM_LEFT: // 左下角 newWidth = (int) (mFixedCornerX - currentX); newHeight = (int) (currentY - mFixedCornerY); newX = (int) currentX; break; case CORNER_BOTTOM_RIGHT: // 右下角 newWidth = (int) (currentX - mFixedCornerX); newHeight = (int) (currentY - mFixedCornerY); break; } // 应用尺寸限制并保持最小尺寸 newWidth = Math.max(MIN_SIZE, Math.min(newWidth, MAX_SIZE)); newHeight = Math.max(MIN_SIZE, Math.min(newHeight, MAX_SIZE)); // 根据限制后的尺寸调整位置(保持固定点不变) switch (mCornerType) { case CORNER_RIGHT: case CORNER_LEFT: case CORNER_BOTTOM_LEFT: newX = mFixedCornerX - newWidth; break; } // 边界保护:确保窗口不会超出屏幕 int screenWidth = DisplayUtil.getScreenWidth(v.getContext()); int screenHeight = DisplayUtil.getScreenHeight(v.getContext()); // X轴边界保护 if (newX < 0) { newWidth += newX; // 减少宽度 newX = 0; } else if (newX + newWidth > screenWidth) { newWidth = screenWidth - newX; } // Y轴边界保护 if (newY < 0) { newHeight += newY; // 减少高度 newY = 0; } else if (newY + newHeight > screenHeight) { newHeight = screenHeight - newY; } // 确保尺寸不小于最小值 newWidth = Math.max(MIN_SIZE, newWidth); newHeight = Math.max(MIN_SIZE, newHeight); // 更新窗口尺寸和位置 getEasyWindow().setWidth(newWidth); getEasyWindow().setHeight(newHeight); updateLocation(newX, newY); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: isResizing = false; break; } return true; } return false; } } package com.maiyou.maiysdk.util.window.draggable; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.os.Build; import android.util.TypedValue; import android.view.DisplayCutout; import android.view.Gravity; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.view.View.OnTouchListener; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import com.maiyou.maiysdk.util.window.EasyWindow; /** * author : Android 轮子哥 * github : https://github.com/getActivity/EasyWindow * time : 2019/01/04 * desc : 拖拽抽象类 */ public abstract class BaseDraggable implements OnTouchListener { private EasyWindow<?> mEasyWindow; private View mDecorView; /** 是否允许移动到挖孔屏区域 */ private boolean mAllowMoveToScreenNotch = true; /** 拖拽回调监听 */ private DraggingCallback mDraggingCallback; private final Rect mTempRect = new Rect(); private int mCurrentWindowWidth; private int mCurrentWindowHeight; private int mCurrentViewOnScreenX; private int mCurrentViewOnScreenY; private int mCurrentWindowInvisibleWidth; private int mCurrentWindowInvisibleHeight; /** * 判断当前是否处于触摸移动状态 */ public abstract boolean isTouchMoving(); /** * 窗口显示后回调这个方法 */ @SuppressLint("ClickableViewAccessibility") public void start(EasyWindow<?> easyWindow) { mEasyWindow = easyWindow; mDecorView = easyWindow.getDecorView(); mDecorView.setOnTouchListener(this); mDecorView.post(() -> { refreshWindowInfo(); refreshLocationCoordinate(); }); } public EasyWindow<?> getEasyWindow() { return mEasyWindow; } public View getDecorView() { return mDecorView; } public void setAllowMoveToScreenNotch(boolean allowMoveToScreenNotch) { mAllowMoveToScreenNotch = allowMoveToScreenNotch; } public boolean isAllowMoveToScreenNotch() { return mAllowMoveToScreenNotch; } /** * 获取当前 Window 的宽度 */ public int getWindowWidth() { return mCurrentWindowWidth; } /** * 获取当前 Window 的高度 */ public int getWindowHeight() { return mCurrentWindowHeight; } /** * 获取当前 View 的宽度 */ public int getViewWidth() { return mEasyWindow.getViewWidth(); } /** * 获取当前 View 的高度 */ public int getViewHeight() { return mEasyWindow.getViewHeight(); } /** * 获取窗口不可见的宽度,一般情况下为横屏状态下刘海的高度 */ public int getWindowInvisibleWidth() { return mCurrentWindowInvisibleWidth; } /** * 获取窗口不可见的高度,一般情况下为状态栏的高度 */ public int getWindowInvisibleHeight() { return mCurrentWindowInvisibleHeight; } /** * 获取 View 在当前屏幕的 X 坐标 */ public int getViewOnScreenX() { return mCurrentViewOnScreenX; } /** * 获取 View 在当前屏幕的 Y 坐标 */ public int getViewOnScreenY() { return mCurrentViewOnScreenY; } /** * 刷新当前 Window 信息 */ public void refreshWindowInfo() { Context context = mEasyWindow.getContext(); if (context == null) { return; } View decorView = null; if (context instanceof Activity) { decorView = ((Activity) context).getWindow().getDecorView(); } if (decorView == null) { decorView = getDecorView(); } if (decorView == null) { return; } // Log.i(getClass().getSimpleName(), "刷新当前 Window 信息"); // 这里为什么要这么写,因为发现了鸿蒙手机在进行屏幕旋转的时候 // 回调 onConfigurationChanged 方法的时候获取到这些参数已经变化了 // 所以需要提前记录下来,避免后续进行坐标计算的时候出现问题 decorView.getWindowVisibleDisplayFrame(mTempRect); mCurrentWindowWidth = mTempRect.right - mTempRect.left; mCurrentWindowHeight = mTempRect.bottom - mTempRect.top; mCurrentWindowInvisibleWidth = Math.max(mTempRect.left, 0); mCurrentWindowInvisibleHeight = Math.max(mTempRect.top, 0); /* Log.i(getClass().getSimpleName(), "CurrentWindowWidth = " + mCurrentWindowWidth + ",CurrentWindowHeight = " + mCurrentWindowHeight + ",CurrentWindowInvisibleWidth = " + mCurrentWindowInvisibleWidth + ",CurrentWindowInvisibleHeight = " + mCurrentWindowInvisibleHeight); */ } /** * 刷新当前 View 在屏幕的坐标信息 */ public void refreshLocationCoordinate() { View decorView = getDecorView(); if (decorView == null) { return; } int[] location = new int[2]; decorView.getLocationOnScreen(location); mCurrentViewOnScreenX = location[0]; mCurrentViewOnScreenY = location[1]; } /** * 屏幕方向发生了改变 */ public void onScreenOrientationChange() { // Log.i(getClass().getSimpleName(), "屏幕方向发生了改变"); long refreshDelayMillis = 100; if (!isFollowScreenRotationChanges()) { getEasyWindow().postDelayed(() -> { refreshWindowInfo(); refreshLocationCoordinate(); }, refreshDelayMillis); return; } int viewWidth = getDecorView().getWidth(); int viewHeight = getDecorView().getHeight(); // Log.i(getClass().getSimpleName(), "当前 ViewWidth = " + viewWidth + ",ViewHeight = " + viewHeight); int startX = mCurrentViewOnScreenX - mCurrentWindowInvisibleWidth; int startY = mCurrentViewOnScreenY - mCurrentWindowInvisibleHeight; float percentX; // 这里为什么用 getMinTouchDistance(),而不是 0? // 因为其实用 getLocationOnScreen 测量出来的值不太准,有时候是 0,有时候是 1,有时候 2 // 但大多数情况是 0 和 1,这里为了兼容这种误差,使用了最小触摸距离来作为基准值 float minTouchDistance = getMinTouchDistance(); if (startX <= minTouchDistance) { percentX = 0; } else if (Math.abs(mCurrentWindowWidth - (startX + viewWidth)) < minTouchDistance) { percentX = 1; } else { float centerX = startX + viewWidth / 2f; percentX = centerX / mCurrentWindowWidth; } float percentY; if (startY <= minTouchDistance) { percentY = 0; } else if (Math.abs(mCurrentWindowHeight - (startY + viewHeight)) < minTouchDistance) { percentY = 1; } else { float centerY = startY + viewHeight / 2f; percentY = centerY / mCurrentWindowHeight; } View decorView = getDecorView(); if (decorView == null) { return; } // Github issue 地址:https://github.com/getActivity/EasyWindow/issues/49 // 修复在竖屏状态下,先锁屏,再旋转到横屏,后进行解锁,出现的 View.getWindowVisibleDisplayFrame 计算有问题的 Bug // 这是因为屏幕在旋转的时候,视图正处于改变状态,此时通过 View 获取窗口可视区域是有问题,会获取到旧的可视区域 // 解决方案是监听一下 View 布局变化监听,在收到回调的时候再去获取 View 获取窗口可视区域 decorView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { view.removeOnLayoutChangeListener(this); view.postDelayed(() -> { // 先刷新当前窗口信息 refreshWindowInfo(); int x = Math.max((int) (mCurrentWindowWidth * percentX - viewWidth / 2f), 0); int y = Math.max((int) (mCurrentWindowHeight * percentY - viewWidth / 2f), 0); updateLocation(x, y); // 需要注意,这里需要延迟执行,否则会有问题 view.post(() -> onScreenRotateInfluenceCoordinateChangeFinish()); }, refreshDelayMillis); } }); } /** * 屏幕旋转导致悬浮窗坐标发生变化完成方法 */ protected void onScreenRotateInfluenceCoordinateChangeFinish() { refreshWindowInfo(); refreshLocationCoordinate(); } /** * 悬浮窗是否跟随屏幕方向变化而发生变化 */ public boolean isFollowScreenRotationChanges() { return true; } public void updateLocation(float x, float y) { updateLocation(x, y, isAllowMoveToScreenNotch()); } public void updateLocation(float x, float y, boolean allowMoveToScreenNotch) { updateLocation((int) x, (int) y, allowMoveToScreenNotch); } /** * 更新悬浮窗的位置 * * @param x x 坐标(相对与屏幕左上位置) * @param y y 坐标(相对与屏幕左上位置) * @param allowMoveToScreenNotch 是否允许移动到挖孔屏的区域 */ public void updateLocation(int x, int y, boolean allowMoveToScreenNotch) { if (allowMoveToScreenNotch) { updateWindowCoordinate(x, y); return; } Rect safeInsetRect = getSafeInsetRect(); if (safeInsetRect == null) { updateWindowCoordinate(x, y); return; } if (safeInsetRect.left > 0 && safeInsetRect.right > 0 && safeInsetRect.top > 0 && safeInsetRect.bottom > 0) { updateWindowCoordinate(x, y); return; } int viewWidth = mEasyWindow.getViewWidth(); int viewHeight = mEasyWindow.getViewHeight(); int windowWidth = getWindowWidth(); int windowHeight = getWindowHeight(); // Log.i(getClass().getSimpleName(), "开始 x 坐标为:" + x); // Log.i(getClass().getSimpleName(), "开始 y 坐标为:" + y); if (x < safeInsetRect.left - getWindowInvisibleWidth()) { x = safeInsetRect.left - getWindowInvisibleWidth(); // Log.i(getClass().getSimpleName(), "x 坐标已经触碰到屏幕左侧的安全区域"); } else if (x > windowWidth - safeInsetRect.right - viewWidth) { x = windowWidth - safeInsetRect.right - viewWidth; // Log.i(getClass().getSimpleName(), "x 坐标已经触碰到屏幕右侧的安全区域"); } // Log.i(getClass().getSimpleName(), "最终 x 坐标为:" + x); if (y < safeInsetRect.top - getWindowInvisibleHeight()) { y = safeInsetRect.top - getWindowInvisibleHeight(); // Log.i(getClass().getSimpleName(), "y 坐标已经触碰到屏幕顶侧的安全区域"); } else if (y > windowHeight - safeInsetRect.bottom - viewHeight) { y = windowHeight - safeInsetRect.bottom - viewHeight; // Log.i(getClass().getSimpleName(), "y 坐标已经触碰到屏幕底部的安全区域"); } // Log.i(getClass().getSimpleName(), "最终 y 坐标为:" + y); updateWindowCoordinate(x, y); } public void updateWindowCoordinate(int x, int y) { LayoutParams params = mEasyWindow.getWindowParams(); if (params == null) { return; } // 屏幕默认的重心(一定要先设置重心位置为左上角) int screenGravity = Gravity.TOP | Gravity.START; // 判断本次移动的位置是否跟当前的窗口位置是否一致 if (params.gravity == screenGravity && params.x == x && params.y == y) { return; } params.x = x; params.y = y; params.gravity = screenGravity; mEasyWindow.update(); refreshLocationCoordinate(); } public Rect getSafeInsetRect() { Context context = mEasyWindow.getContext(); Window window; if (!(context instanceof Activity)) { return null; } window = ((Activity) context).getWindow(); if (window == null) { return null; } return getSafeInsetRect(window); } /** * 获取屏幕安全区域位置(返回的对象可能为空) */ public static Rect getSafeInsetRect(Window window) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return null; } View activityDecorView = null; if (window != null) { activityDecorView = window.getDecorView(); } WindowInsets rootWindowInsets = null; if (activityDecorView != null) { rootWindowInsets = activityDecorView.getRootWindowInsets(); } DisplayCutout displayCutout = null; if (rootWindowInsets != null) { displayCutout = rootWindowInsets.getDisplayCutout(); } if (displayCutout != null) { // 安全区域距离屏幕左边的距离 int safeInsetLeft = displayCutout.getSafeInsetLeft(); // 安全区域距离屏幕顶部的距离 int safeInsetTop = displayCutout.getSafeInsetTop(); // 安全区域距离屏幕右部的距离 int safeInsetRight = displayCutout.getSafeInsetRight(); // 安全区域距离屏幕底部的距离 int safeInsetBottom = displayCutout.getSafeInsetBottom(); // Log.i(getClass().getSimpleName(), "安全区域距离屏幕左侧的距离 SafeInsetLeft:" + safeInsetLeft); // Log.i(getClass().getSimpleName(), "安全区域距离屏幕右侧的距离 SafeInsetRight:" + safeInsetRight); // Log.i(getClass().getSimpleName(), "安全区域距离屏幕顶部的距离 SafeInsetTop:" + safeInsetTop); // Log.i(getClass().getSimpleName(), "安全区域距离屏幕底部的距离 SafeInsetBottom:" + safeInsetBottom); return new Rect(safeInsetLeft, safeInsetTop, safeInsetRight, safeInsetBottom); } return null; } /** * 判断用户手指是否移动了,判断标准以下: * 根据手指按下和抬起时的坐标进行判断,不能根据有没有 move 事件来判断 * 因为在有些机型上面,就算用户没有手指移动,只是简单点击也会产生 move 事件 * * @param downX 手指按下时的 x 坐标 * @param upX 手指抬起时的 x 坐标 * @param downY 手指按下时的 y 坐标 * @param upY 手指抬起时的 y 坐标 */ protected boolean isFingerMove(float downX, float upX, float downY, float upY) { float minTouchSlop = getMinTouchDistance(); return Math.abs(downX - upX) >= minTouchSlop || Math.abs(downY - upY) >= minTouchSlop; } /** * 判断当前悬浮窗是否可以移动到屏幕之外的地方 */ protected boolean isSupportMoveOffScreen() { return mEasyWindow.hasWindowFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS); } /** * 获取最小触摸距离 */ protected float getMinTouchDistance() { // 疑问一:为什么要使用 1dp 来作为最小触摸距离? // 这是因为用户点击的时候,手指 down 和 up 的坐标不相等,会存在一点误差 // 在有些手机上面,误差会比较小,还有一些手机上面,误差会比较大 // 经过拿不同的手机测试和验证,这个误差值可以锁定在 1dp 内 // 当然我的结论不一定正确,你要是有发现新的问题也可以找我反馈,我会持续优化这个问题 // 疑问二:为什么不使用 ViewConfiguration.get(context).getScaledTouchSlop() ? // 这是因为这个 API 获取到的数值太大了,有一定概率会出现误判,同样的手机上面 // 用 getScaledTouchSlop 获取到的是 24,而系统 1dp 获取的到是 3, // 两者相差太大,因为 getScaledTouchSlop API 默认获取的是 8dp * 3 = 24px // 疑问三:为什么要用 Resources.getSystem 来获取,而不是 context.getResources? // 这是因为如果用了 AutoSize 这个框架,上下文中的 1dp 就不是 3px 了 // 使用 Resources.getSystem 能够保证 Resources 对象 dp 计算规则不被第三方框架篡改 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, Resources.getSystem().getDisplayMetrics()); } /** * 设置拖拽回调 */ public void setDraggingCallback(DraggingCallback callback) { mDraggingCallback = callback; } /** * 派发开始拖拽事件 */ protected void dispatchStartDraggingCallback() { // Log.i(getClass().getSimpleName(), "开始拖拽"); if (mDraggingCallback == null) { return; } mDraggingCallback.onStartDragging(mEasyWindow); } /** * 派发拖拽中事件 */ protected void dispatchExecuteDraggingCallback() { // Log.i(getClass().getSimpleName(), "拖拽中"); if (mDraggingCallback == null) { return; } mDraggingCallback.onExecuteDragging(mEasyWindow); } /** * 派发停止拖拽事件 */ protected void dispatchStopDraggingCallback() { // Log.i(getClass().getSimpleName(), "停止拖拽"); if (mDraggingCallback == null) { return; } mDraggingCallback.onStopDragging(mEasyWindow); } public interface DraggingCallback { /** * 开始拖拽 */ default void onStartDragging(EasyWindow<?> easyWindow) {} /** * 执行拖拽中 */ default void onExecuteDragging(EasyWindow<?> easyWindow) {} /** * 停止拖拽 */ default void onStopDragging(EasyWindow<?> easyWindow) {} } }
最新发布
07-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值