From f3aaf8fc35ec8e06b5561f9b1e9c23a8c2fb853c Mon Sep 17 00:00:00 2001 From: 2995840376 <2995840376@xiaomi.com> Date: Wed, 28 Oct 2026 11:11:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(P):=20Android=20=E9=A6=96=E9=A1=B5=E5=B8=B8?= =?UTF-8?q?=E7=94=A8=E8=AE=BE=E5=A4=87=E4=B8=8D=E5=86=8D=E5=9B=A0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E8=80=8C=E9=87=8D=E6=8E=92/=E9=97=AA=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 会话内顺序稳定: 记录上次渲染顺序, 新设备追加末尾, 后端顺序变化也不跳位 - 切家庭时重置顺序基准 - 点击卡片开关时通知页面: DP 回传期间跳过整表刷新, 4s 后延迟对齐一次 --- .../java/com/xhai/iot/ui/HomeFragment.java | 105 +++++++++++++++++- .../iot/ui/adapter/HomeDeviceAdapter.java | 19 ++++ 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/Android/app/src/main/java/com/xhai/iot/ui/HomeFragment.java b/Android/app/src/main/java/com/xhai/iot/ui/HomeFragment.java index bb21373..a78765f 100644 --- a/Android/app/src/main/java/com/xhai/iot/ui/HomeFragment.java +++ b/Android/app/src/main/java/com/xhai/iot/ui/HomeFragment.java @@ -108,6 +108,9 @@ public class HomeFragment extends Fragment { activityRefreshHandler.removeCallbacks(activityRefreshTask); activityRefreshTask = null; } + if (deviceRefreshHandler != null) { + deviceRefreshHandler.removeCallbacks(alignRefreshTask); + } } @Override @@ -195,7 +198,14 @@ public class HomeFragment extends Fragment { public void onDpUpdate(String devId, Map dps) { android.util.Log.i("HomeFragment", "DP回调[" + devId + "] dps=" + dps); if (rvDevices != null) { - rvDevices.post(HomeFragment.this::scheduleDeviceRefresh); + if (isSelfInitiatedControl(devId)) { + // 用户刚点了这张卡片的开关:胶囊已按乐观值显示, + // 此时整表重载会让"常用设备"整块闪一下(顺序已稳定,但仍有重绘)。 + // 推迟到窗口结束后再对齐一次即可。 + scheduleDelayedAlignRefresh(); + } else { + rvDevices.post(HomeFragment.this::scheduleDeviceRefresh); + } } // DP 变化统一上报后端(source=panel): // 这是蓝牙 Mesh 本地通道控制的唯一上报路径——手机蓝牙直连 publishDps @@ -215,6 +225,39 @@ public class HomeFragment extends Fragment { } }; + // ==================== 控制后延迟对齐(避免常用设备整表闪动) ==================== + + /** 用户刚点击控制的设备ID */ + private String pendingControlDeviceId; + /** 用户刚点击控制的时间戳 */ + private long pendingControlAt; + /** 控制后延迟对齐刷新的等待时间 */ + private static final long CONTROL_ALIGN_DELAY_MS = 4000L; + /** 控制后延迟对齐刷新任务 */ + private final Runnable alignRefreshTask = new Runnable() { + @Override + public void run() { + if (isAdded()) { + loadDevicesFromBackend(); + } + } + }; + + /** 该 DP 变化是否来自用户刚在首页点下的控制 */ + private boolean isSelfInitiatedControl(String devId) { + return devId != null && devId.equals(pendingControlDeviceId) + && System.currentTimeMillis() - pendingControlAt < CONTROL_ALIGN_DELAY_MS; + } + + /** 控制后延迟对齐刷新:等指令回传稳定,再拉一次列表让服务端状态覆盖乐观值 */ + private void scheduleDelayedAlignRefresh() { + if (rvDevices == null) return; + rvDevices.post(() -> { + deviceRefreshHandler.removeCallbacks(alignRefreshTask); + deviceRefreshHandler.postDelayed(alignRefreshTask, CONTROL_ALIGN_DELAY_MS); + }); + } + private void initViews(View root) { rvDevices = root.findViewById(R.id.rv_devices); progressBar = root.findViewById(R.id.progress_devices); @@ -265,6 +308,11 @@ public class HomeFragment extends Fragment { adapter = new HomeDeviceAdapter(new ArrayList<>(), (device, position) -> openDevicePanel(device)); adapter.setAccentColor(accent); + // 用户点了卡片开关:记录下来,DP 回调时不做整表刷新(避免"常用设备"闪动/跳位) + adapter.setOnControlStartListener(deviceId -> { + pendingControlDeviceId = deviceId; + pendingControlAt = System.currentTimeMillis(); + }); // 长按设备:编辑(重命名)/ 删除 adapter.setOnDeviceLongClickListener((device, position) -> DeviceActionDialogs.showActions(requireActivity(), @@ -410,6 +458,8 @@ public class HomeFragment extends Fragment { Toast.makeText(requireContext(), "已切换到「" + (home.getName() != null ? home.getName() : "家庭") + "」", Toast.LENGTH_SHORT).show(); + // 换家庭:上一次的设备顺序不再适用,重置后按新家庭顺序展示 + lastDeviceOrder.clear(); loadDevicesFromBackend(); loadRecentActivities(); } @@ -1160,11 +1210,62 @@ public class HomeFragment extends Fragment { } result.add(td); } - // 在线设备排前面,离线排后面(组内保持原顺序) + // 1) 先按「上一次显示的顺序」重排:设备位置对用户是固定的, + // 不能因为一次控制上报就让常用设备换位置(JD2BTSA 跳到 JD6BTSA 前面)。 + // 新增设备按后端返回顺序追加到末尾。 + applyStableOrder(result); + // 2) 在线设备排前面,离线排后面(稳定排序,组内保持上一步的相对顺序) result.sort((a, b) -> Boolean.compare(!a.isOnline(), !b.isOnline())); return result; } + /** 上一次渲染的设备顺序(设备ID),用于保持列表位置稳定 */ + private final List lastDeviceOrder = new ArrayList<>(); + + /** + * 按上次顺序重排设备列表。 + * + *

后端已对 member 视角改为按 id 升序(位置固定),这里再加一层会话内兜底: + * 万一后端顺序变化(如新设备插入、后台排序调整),也不让已在屏上的设备跳位。

+ */ + private void applyStableOrder(List devices) { + if (devices == null || devices.isEmpty()) { + lastDeviceOrder.clear(); + return; + } + if (lastDeviceOrder.isEmpty()) { + // 首次加载:以本次顺序为基准 + lastDeviceOrder.clear(); + for (TuyaDevice d : devices) { + lastDeviceOrder.add(d.getId()); + } + return; + } + java.util.Map remain = new java.util.LinkedHashMap<>(); + for (TuyaDevice d : devices) { + remain.put(d.getId(), d); + } + List ordered = new ArrayList<>(devices.size()); + for (String id : lastDeviceOrder) { + TuyaDevice d = remain.remove(id); + if (d != null) { + ordered.add(d); + } + } + // 新设备追加在后面 + for (TuyaDevice d : devices) { + if (remain.containsKey(d.getId())) { + ordered.add(d); + } + } + devices.clear(); + devices.addAll(ordered); + lastDeviceOrder.clear(); + for (TuyaDevice d : ordered) { + lastDeviceOrder.add(d.getId()); + } + } + private void showLoading(boolean show) { progressBar.setVisibility(show ? View.VISIBLE : View.GONE); } diff --git a/Android/app/src/main/java/com/xhai/iot/ui/adapter/HomeDeviceAdapter.java b/Android/app/src/main/java/com/xhai/iot/ui/adapter/HomeDeviceAdapter.java index 86db299..4f8bdcc 100644 --- a/Android/app/src/main/java/com/xhai/iot/ui/adapter/HomeDeviceAdapter.java +++ b/Android/app/src/main/java/com/xhai/iot/ui/adapter/HomeDeviceAdapter.java @@ -52,6 +52,21 @@ public class HomeDeviceAdapter extends RecyclerView.Adapter用户在卡片上点了开关时通知页面,便于页面推迟整表刷新(避免列表重排/闪动)。

+ */ + public interface OnControlStart { + void onControlStart(String deviceId); + } + + private OnControlStart controlStartListener; + + public void setOnControlStartListener(OnControlStart l) { + this.controlStartListener = l; + } + public HomeDeviceAdapter(List devices, OnDeviceClick listener) { this.devices = devices; this.listener = listener; @@ -215,6 +230,10 @@ public class HomeDeviceAdapter extends RecyclerView.Adapter { // 锁定乐观值:下发结果回传前,忽略列表刷新带回来的旧状态 lockPendingToggle(device.getId(), checked); + // 通知页面:用户刚点了开关,别急着整表刷新(会闪动/跳位) + if (controlStartListener != null) { + controlStartListener.onControlStart(device.getId()); + } Map commands = new HashMap<>(); commands.put(switchDpId, checked); PlatformServiceManager.getInstance(holder.itemView.getContext())