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())