addOnLayoutChangeListener

博客介绍了在Android中监听ListView加载完毕的方法,即使用addOnLayoutChangeListener。这是信息技术领域中移动开发方面的实用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

监听listview加载完毕:
addOnLayoutChangeListener

优化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、付费专栏及课程。

余额充值