Skip to content

[建议]:AbstractWindowDraggableRule 横屏贴边失败,导致左侧贴边时,会保留通知栏的高度, #91

@andoridhyc

Description

@andoridhyc

你觉得框架有什么不足之处?【必答】

我做了以下关键改动说明(便于你快速 review):

refreshWindowInfo():

使用 getRealMetrics() 获取真实屏幕宽高赋给 mCurrentWindowWidth / mCurrentWindowHeight(fallback 保持兼容)。

仍然用 getWindowVisibleDisplayFrame() 得到 mTempRect 来计算 mCurrentWindowInvisibleWidth / mCurrentWindowInvisibleHeight(用于在不允许进入刘海区时做限制)。

updateLocation(int x, int y, boolean allowMoveToScreenNotch):

如果允许进入刘海区:使用真实屏幕宽高作为边界(可以移动到屏幕任意位置)。

如果不允许进入刘海区:尝试使用 getSafeInsetRect()(若完整有效则用 real screen 与 inset 约束;若无则退回到 visible-area 约束)。

updateWindowCoordinate(...):

在写入 LayoutParams 时(API 28+),若允许进入刘海区则将 layoutInDisplayCutoutMode 置为 LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES。

新增 getRealScreenWidth() / getRealScreenHeight() 便于获取真实尺寸。

issue 是否有人曾提过类似的建议?【必答】

框架文档是否提及了该问题【必答】

是否已经查阅框架文档但还未能解决的【必答】

你觉得该怎么去完善会比较好?【非必答】

package com.huoban.ai.huobanai.ease.window.draggable;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.webkit.WebView;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.SeekBar;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingChild;
import androidx.core.view.NestedScrollingParent;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;

import com.huoban.ai.huobanai.ease.window.EasyWindow;

/**

  • author : Android 轮子哥(基于你提供源码做“终极精准修复”)
  • github : https://github.com/getActivity/EasyWindow
  • time : 2019/01/04(修改:终极贴边版本)
  • desc : 拖拽抽象类(已增强:真实屏幕尺寸 + 刘海适配)
    */
    public abstract class AbstractWindowDraggableRule implements OnTouchListener {
@Nullable
private EasyWindow<?> mEasyWindow;
@Nullable
private ViewGroup mRootLayout;

/** 是否允许移动到挖孔屏区域 */
private boolean mAllowMoveToScreenNotch = true;

/** 拖拽回调监听对象(可能为空) */
@Nullable
private OnWindowDraggingListener mWindowDraggingListener;

@NonNull
private final Rect mTempRect = new Rect();

private int mCurrentWindowWidth;
private int mCurrentWindowHeight;
private int mCurrentViewOnScreenX;
private int mCurrentViewOnScreenY;
private int mCurrentWindowInvisibleWidth;
private int mCurrentWindowInvisibleHeight;

/** 当前屏幕物理尺寸 */
private double mPhysicalScreenSize;

/** 需要消费触摸事件的 View(可能为空)*/
@Nullable
private View mConsumeTouchView;

/**
 * 判断当前是否处于触摸移动状态
 */
public abstract boolean isTouchMoving();

/**
 * 窗口显示后回调这个方法
 */
@SuppressLint("ClickableViewAccessibility")
public void start(@NonNull EasyWindow<?> easyWindow) {
    mEasyWindow = easyWindow;
    mRootLayout = easyWindow.getRootLayout();
    if (mRootLayout == null) {
        return;
    }
    mRootLayout.setOnTouchListener(this);
    mRootLayout.post(() -> {
        refreshWindowInfo();
        refreshPhysicalScreenSize();
        refreshLocationCoordinate();
    });
}

/**
 * 窗口回收后回调这个方法
 */
public void recycle() {
    mEasyWindow = null;
    if (mRootLayout != null) {
        mRootLayout.setOnTouchListener(null);
        mRootLayout = null;
    }
}

@Override
public final boolean onTouch(View view, MotionEvent event) {
    if (mEasyWindow == null || mRootLayout == null) {
        return false;
    }

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 在按下的时候先更新一下窗口信息和坐标信息,否则点击可能会出现坐标偏移的问题
            refreshWindowInfo();
            refreshPhysicalScreenSize();
            refreshLocationCoordinate();

            mConsumeTouchView = null;
            View consumeTouchEventView = findNeedConsumeTouchView(mRootLayout, event);
            if (consumeTouchEventView != null && dispatchTouchEventToChildView(mRootLayout, consumeTouchEventView, event)) {
                mConsumeTouchView = consumeTouchEventView;
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (mConsumeTouchView != null) {
                try {
                    return dispatchTouchEventToChildView(mRootLayout, mConsumeTouchView, event);
                } finally {
                    // 释放/置空对象
                    mConsumeTouchView = null;
                }
            }
        default:
            if (mConsumeTouchView != null) {
                return dispatchTouchEventToChildView(mRootLayout, mConsumeTouchView, event);
            }
            break;
    }

    return onDragWindow(mEasyWindow, mRootLayout, event);
}

/**
 * 派发触摸事件给子 View
 *     * @param event                 触摸事件
 * @param parentView            父 View
 * @param childView             子 View(同时也是被触摸的 View)
 */
public boolean dispatchTouchEventToChildView(@NonNull View parentView, @NonNull View childView, @NonNull MotionEvent event) {
    // 派发触摸事件之前,先将 MotionEvent 对象中的位置进行纠偏,否则会导致点击坐标对不上的情况
    offsetMotionEventLocation(parentView, childView, event);
    return childView.dispatchTouchEvent(event);
}

/**
 * 偏移触摸事件坐标,这样子 View 能接受到正确的坐标
 *
 * @param event                 触摸事件
 * @param parentView            父 View
 * @param childView             子 View(同时也是被触摸的 View)
 */
public void offsetMotionEventLocation(@NonNull View parentView, @NonNull View childView, @NonNull MotionEvent event) {
    // 这部分代码参考自 ViewGroup.dispatchTransformedTouchEvent 方法实现
    final int offsetX = parentView.getScrollX() - childView.getLeft();
    final int offsetY = parentView.getScrollY() - childView.getTop();
    event.offsetLocation(offsetX, offsetY);
}

/**
 * 窗口拖拽回调方法
 *
 * @param easyWindow        当前窗口对象
 * @param windowRootLayout  当前窗口视图
 * @param event             当前触摸事件
 * @return                  根据返回值决定是否拦截该事件
 */
public abstract boolean onDragWindow(@NonNull EasyWindow<?> easyWindow, @NonNull ViewGroup windowRootLayout, @NonNull MotionEvent event);

@Nullable
public EasyWindow<?> getEasyWindow() {
    return mEasyWindow;
}

@Nullable
public ViewGroup getRootLayout() {
    return mRootLayout;
}

public void setAllowMoveToScreenNotch(boolean allowMoveToScreenNotch) {
    mAllowMoveToScreenNotch = allowMoveToScreenNotch;
}

public boolean isAllowMoveToScreenNotch() {
    return mAllowMoveToScreenNotch;
}

/**
 * 获取当前 Window 的宽度(已使用真实屏幕宽度)
 */
public int getWindowWidth() {
    return mCurrentWindowWidth;
}

/**
 * 获取当前 Window 的高度(已使用真实屏幕高度)
 */
public int getWindowHeight() {
    return mCurrentWindowHeight;
}

/**
 * 获取当前窗口视图的宽度
 */
public int getWindowViewWidth() {
    if (mEasyWindow == null) {
        return 0;
    }
    return mEasyWindow.getWindowViewWidth();
}

/**
 * 获取当前窗口视图的高度
 */
public int getWindowViewHeight() {
    if (mEasyWindow == null) {
        return 0;
    }
    return mEasyWindow.getWindowViewHeight();
}

/**
 * 获取窗口不可见的宽度,一般情况下为横屏状态下刘海的高度
 */
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 信息(改为使用 real metrics / real size)
 */
@SuppressWarnings("deprecation")
public void refreshWindowInfo() {
    if (mEasyWindow == null) {
        return;
    }

    Context context = mEasyWindow.getContext();
    if (context == null) {
        return;
    }

    View decorView = getRootLayout();

    if (decorView == null && context instanceof Activity) {
        decorView = ((Activity) context).getWindow().getDecorView();
    }

    if (decorView == null) {
        return;
    }

    WindowManager windowManager = mEasyWindow.getWindowManager();
    Display defaultDisplay = windowManager.getDefaultDisplay();
    if (defaultDisplay == null) {
        return;
    }

    // 获取真实屏幕尺寸(不受系统栏 / 导航栏裁剪影响)
    DisplayMetrics realMetrics = new DisplayMetrics();
    try {
        defaultDisplay.getRealMetrics(realMetrics);
        mCurrentWindowWidth = realMetrics.widthPixels;
        mCurrentWindowHeight = realMetrics.heightPixels;
    } catch (Exception ignored) {
        // fallback to visible display frame if getRealMetrics not available
        decorView.getWindowVisibleDisplayFrame(mTempRect);
        mCurrentWindowWidth = mTempRect.right - mTempRect.left;
        mCurrentWindowHeight = mTempRect.bottom - mTempRect.top;
    }

    // 同时获取可视区域,计算不可见的宽/高(用于在“不允许进入刘海/系统区”时做限制)
    decorView.getWindowVisibleDisplayFrame(mTempRect);
    mCurrentWindowInvisibleWidth = Math.max(mTempRect.left, 0);
    mCurrentWindowInvisibleHeight = Math.max(mTempRect.top, 0);
}

/**
 * 刷新当前设备的物理屏幕尺寸
 */
@SuppressWarnings("deprecation")
public void refreshPhysicalScreenSize() {
    if (mEasyWindow == null) {
        return;
    }

    WindowManager windowManager = mEasyWindow.getWindowManager();
    Display defaultDisplay = windowManager.getDefaultDisplay();
    if (defaultDisplay == null) {
        return;
    }

    DisplayMetrics metrics = new DisplayMetrics();
    defaultDisplay.getMetrics(metrics);

    float screenWidthInInches;
    float screenHeightInInches;
    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
        Point point = new Point();
        defaultDisplay.getRealSize(point);
        screenWidthInInches = point.x / metrics.xdpi;
        screenHeightInInches = point.y / metrics.ydpi;
    } else {
        screenWidthInInches = metrics.widthPixels / metrics.xdpi;
        screenHeightInInches = metrics.heightPixels / metrics.ydpi;
    }

    // 勾股定理:直角三角形的两条直角边的平方和等于斜边的平方
    mPhysicalScreenSize = Math.sqrt(Math.pow(screenWidthInInches, 2) + Math.pow(screenHeightInInches, 2));
}

/**
 * 刷新当前 View 在屏幕的坐标信息
 */
public void refreshLocationCoordinate() {
    ViewGroup windowRootLayout = getRootLayout();
    if (windowRootLayout == null) {
        return;
    }

    int[] location = new int[2];
    windowRootLayout.getLocationOnScreen(location);
    mCurrentViewOnScreenX = location[0];
    mCurrentViewOnScreenY = location[1];
}

/**
 * 屏幕方向发生了改变
 */
public void onScreenOrientationChange() {
    ViewGroup windowRootLayout = getRootLayout();
    if (windowRootLayout == null) {
        return;
    }

    long refreshDelayMillis = 100;

    if (!isFollowScreenRotationChanges()) {
        EasyWindow<?> easyWindow = getEasyWindow();
        if (easyWindow != null) {
            easyWindow.sendTask(() -> {
                refreshWindowInfo();
                refreshPhysicalScreenSize();
                refreshLocationCoordinate();
            }, refreshDelayMillis);
        }
        return;
    }

    int viewWidth = windowRootLayout.getWidth();
    int viewHeight = windowRootLayout.getHeight();

    int startX = mCurrentViewOnScreenX - mCurrentWindowInvisibleWidth;
    int startY = mCurrentViewOnScreenY - mCurrentWindowInvisibleHeight;

    float percentX;
    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;
    }

    windowRootLayout.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();
                // 刷新屏幕物理尺寸
                refreshPhysicalScreenSize();
                int x = Math.max((int) (mCurrentWindowWidth * percentX - viewWidth / 2f), 0);
                int y = Math.max((int) (mCurrentWindowHeight * percentY - viewHeight / 2f), 0);
                updateLocation(x, y);
                // 需要注意,这里需要延迟执行,否则会有问题
                view.post(() -> onScreenRotateInfluenceCoordinateChangeFinish());
            }, refreshDelayMillis);
        }
    });
}

/**
 * 屏幕旋转导致悬浮窗坐标发生变化完成方法
 */
protected void onScreenRotateInfluenceCoordinateChangeFinish() {
    refreshWindowInfo();
    refreshPhysicalScreenSize();
    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) {
        // 保证 x,y 在真实屏幕范围内
        int realW = getRealScreenWidth();
        int realH = getRealScreenHeight();
        int vw = getWindowViewWidth();
        int vh = getWindowViewHeight();
        int nx = Math.max(0, Math.min(x, Math.max(0, realW - vw)));
        int ny = Math.max(0, Math.min(y, Math.max(0, realH - vh)));
        updateWindowCoordinate(nx, ny);
        return;
    }

    // 不允许进入刘海区域的严格限制(使用系统安全区域)
    Rect safeInsetRect = getSafeInsetRect();
    if (safeInsetRect == null) {
        // 没有安全区域信息则退回到可视区域约束
        // 使用已刷新到的 mCurrentWindowWidth / mCurrentWindowHeight(real metrics)
        int nx = Math.max(0, Math.min(x, Math.max(0, mCurrentWindowWidth - getWindowViewWidth())));
        int ny = Math.max(0, Math.min(y, Math.max(0, mCurrentWindowHeight - getWindowViewHeight())));
        updateWindowCoordinate(nx, ny);
        return;
    }

    // 如果 safeInsetRect 的四边都有值(说明系统给出完整 inset),则直接使用 real 屏幕宽高与 inset 做限制
    if (safeInsetRect.left > 0 && safeInsetRect.right > 0 &&
            safeInsetRect.top > 0 && safeInsetRect.bottom > 0) {

        int vw = getWindowViewWidth();
        int vh = getWindowViewHeight();
        int realW = getRealScreenWidth();
        int realH = getRealScreenHeight();

        if (x < safeInsetRect.left - getWindowInvisibleWidth()) {
            x = safeInsetRect.left - getWindowInvisibleWidth();
        } else if (x > realW - safeInsetRect.right - vw) {
            x = realW - safeInsetRect.right - vw;
        }

        if (y < safeInsetRect.top - getWindowInvisibleHeight()) {
            y = safeInsetRect.top - getWindowInvisibleHeight();
        } else if (y > realH - safeInsetRect.bottom - vh) {
            y = realH - safeInsetRect.bottom - vh;
        }

        updateWindowCoordinate(x, y);
        return;
    }

    // 默认回退逻辑(基于 mCurrentWindowWidth/mCurrentWindowHeight)
    int viewWidth = getWindowViewWidth();
    int viewHeight = getWindowViewHeight();

    int windowWidth = getWindowWidth();
    int windowHeight = getWindowHeight();

    if (x < safeInsetRect.left - getWindowInvisibleWidth()) {
        x = safeInsetRect.left - getWindowInvisibleWidth();
    } else if (x > windowWidth - safeInsetRect.right - viewWidth) {
        x = windowWidth - safeInsetRect.right - viewWidth;
    }

    if (y < safeInsetRect.top - getWindowInvisibleHeight()) {
        y = safeInsetRect.top - getWindowInvisibleHeight();
    } else if (y > windowHeight - safeInsetRect.bottom - viewHeight) {
        y = windowHeight - safeInsetRect.bottom - viewHeight;
    }

    updateWindowCoordinate(x, y);
}

public void updateWindowCoordinate(int x, int y) {
    if (mEasyWindow == null) {
        return;
    }
    WindowManager.LayoutParams params = mEasyWindow.getWindowParams();

    // 屏幕默认的重心(一定要先设置重心位置为左上角)
    int screenGravity = Gravity.LEFT | Gravity.TOP;

    // 判断本次移动的位置是否跟当前的窗口位置是否一致
    if (params.gravity == screenGravity && params.x == x && params.y == y) {
        return;
    }

    params.x = x;
    params.y = y;
    params.gravity = screenGravity;

    // 如果允许移动到刘海区域,尝试打开 layoutInDisplayCutoutMode(API 28+)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        try {
            if (isAllowMoveToScreenNotch()) {
                params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
            }
        } catch (Exception ignored) {
            // 某些 ROM 上可能不支持,忽略即可
        }
    }

    mEasyWindow.update();
    refreshLocationCoordinate();
}

/**
 * 获取当前屏幕安全区域
 */
@Nullable
public Rect getSafeInsetRect() {
    if (mEasyWindow == null) {
        return null;
    }
    Context context = mEasyWindow.getContext();
    Window window;
    if (!(context instanceof Activity)) {
        return null;
    }

    window = ((Activity) context).getWindow();
    if (window == null) {
        return null;
    }

    return getSafeInsetRect(window);
}

/**
 * 根据 Window 对象获取屏幕安全区域位置(返回的对象可能为空)
 */
@Nullable
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();

        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 float getMinTouchDistance() {
    double physicalScreenSize = getPhysicalScreenSize();
    int dpValue;
    if (physicalScreenSize > 0) {
        // 市面上的平板最大尺寸不超过 15 英寸
        dpValue = (int) Math.ceil(physicalScreenSize / 15);
    } else {
        dpValue = 1;
    }
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue,
            Resources.getSystem().getDisplayMetrics());
}

/**
 * 寻找需要消费触摸事件的 View(可能为空)
 */
@Nullable
protected View findNeedConsumeTouchView(ViewGroup viewGroup, MotionEvent event) {
    int childCount = viewGroup.getChildCount();
    for (int i = 0; i < childCount; i++) {

        View childView = viewGroup.getChildAt(i);
        int[] location = new int[2];
        childView.getLocationOnScreen(location);
        int left = location[0];
        int top = location[1];
        int right = left + childView.getWidth();
        int bottom = top + childView.getHeight();

        float x = event.getRawX();
        float y = event.getRawY();

        // 判断触摸位置是否在这个 View 内
        if (x >= left && x <= right && y >= top && y <= bottom) {
            if (isViewNeedConsumeTouchEvent(childView)) {
                return childView;
            } else if (childView instanceof ViewGroup) {
                return findNeedConsumeTouchView((ViewGroup) childView, event);
            }
        }
    }
    return null;
}

/**
 * 判断 View 是否需要消费当前触摸事件
 */
protected boolean isViewNeedConsumeTouchEvent(@NonNull View view) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && view instanceof ViewGroup && view.isScrollContainer()) {
        return canTouchByView(view);
    }

    if (view instanceof WebView || view instanceof ScrollView || view instanceof ListView || view instanceof SeekBar) {
        return canTouchByView(view);
    }

    // NestedScrollingChild 的子类有:RecyclerView、NestedScrollView、SwipeRefreshLayout 等等
    if (view instanceof NestedScrollingChild || view instanceof NestedScrollingParent || view instanceof ViewPager) {
        return canTouchByView(view);
    }

    Class<? extends View> viewClass = view.getClass();
    try {
        if (viewClass.isAssignableFrom(Class.forName("androidx.viewpager2.widget.ViewPager2"))) {
            return canTouchByView(view);
        }
    } catch (ClassNotFoundException ignored) {
        // default implementation ignored
    }

    return false;
}

/**
 * 判断 View 是否能被触摸
 */
protected boolean canTouchByView(@NonNull View view) {
    if (view instanceof RecyclerView && !canScrollByRecyclerView(((RecyclerView) view))) {
        // 如果这个 RecyclerView 禁止了触摸事件,就不要启动触摸事件
        return false;
    }

    // 这个 View 必须是启用状态,才认为有可能传递触摸事件
    return view.isEnabled();
}

/**
 * 判断 RecyclerView 是否能被触摸
 */
protected boolean canScrollByRecyclerView(@NonNull RecyclerView recyclerView) {
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    if (layoutManager == null) {
        // 如果没有设置 LayoutManager,则默认不需要触摸事件
        return false;
    }

    // 当前这个 LayoutManager 必须开启垂直滚动或者水平滚动
    return layoutManager.canScrollVertically() || layoutManager.canScrollHorizontally();
}

/**
 * 获取物理的屏幕尺寸
 */
protected double getPhysicalScreenSize() {
    return mPhysicalScreenSize;
}

/**
 * 判断当前悬浮窗是否可以移动到屏幕之外的地方
 */
protected boolean isSupportMoveOffScreen() {
    if (mEasyWindow == null) {
        return false;
    }
    return mEasyWindow.hasWindowFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}

/**
 * 设置拖拽回调
 */
public void setWindowDraggingListener(OnWindowDraggingListener callback) {
    mWindowDraggingListener = callback;
}

/**
 * 派发开始拖拽事件
 */
protected void dispatchStartDraggingCallback() {
    if (mEasyWindow == null) {
        return;
    }
    if (mWindowDraggingListener == null) {
        return;
    }
    mWindowDraggingListener.onWindowDraggingStart(mEasyWindow);
}

/**
 * 派发拖拽中事件
 */
protected void dispatchRunningDraggingCallback() {
    if (mEasyWindow == null) {
        return;
    }
    if (mWindowDraggingListener == null) {
        return;
    }
    mWindowDraggingListener.onWindowDraggingRunning(mEasyWindow);
}

/**
 * 派发停止拖拽事件
 */
protected void dispatchStopDraggingCallback() {
    if (mEasyWindow == null) {
        return;
    }
    if (mWindowDraggingListener == null) {
        return;
    }
    mWindowDraggingListener.onWindowDraggingStop(mEasyWindow);
}

public interface OnWindowDraggingListener {

    /**
     * 开始拖拽
     */
    default void onWindowDraggingStart(@NonNull EasyWindow<?> easyWindow) {
        // default implementation ignored
    }

    /**
     * 拖拽中
     */
    default void onWindowDraggingRunning(@NonNull EasyWindow<?> easyWindow) {
        // default implementation ignored
    }

    /**
     * 停止拖拽
     */
    default void onWindowDraggingStop(@NonNull EasyWindow<?> easyWindow) {
        // default implementation ignored
    }
}

/**
 * 获取真实屏幕宽度(px)
 */
protected int getRealScreenWidth() {
    if (mEasyWindow == null) {
        return mCurrentWindowWidth;
    }
    WindowManager wm = mEasyWindow.getWindowManager();
    Display d = wm.getDefaultDisplay();
    Point p = new Point();
    try {
        d.getRealSize(p);
        return p.x;
    } catch (Exception ignored) {
        return mCurrentWindowWidth;
    }
}

/**
 * 获取真实屏幕高度(px)
 */
protected int getRealScreenHeight() {
    if (mEasyWindow == null) {
        return mCurrentWindowHeight;
    }
    WindowManager wm = mEasyWindow.getWindowManager();
    Display d = wm.getDefaultDisplay();
    Point p = new Point();
    try {
        d.getRealSize(p);
        return p.y;
    } catch (Exception ignored) {
        return mCurrentWindowHeight;
    }
}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions