fix(P): Android 首页常用设备不再因控制而重排/闪动
- 会话内顺序稳定: 记录上次渲染顺序, 新设备追加末尾, 后端顺序变化也不跳位 - 切家庭时重置顺序基准 - 点击卡片开关时通知页面: DP 回传期间跳过整表刷新, 4s 后延迟对齐一次
This commit is contained in:
parent
8a11c78d22
commit
f3aaf8fc35
@ -108,6 +108,9 @@ public class HomeFragment extends Fragment {
|
||||
activityRefreshHandler.removeCallbacks(activityRefreshTask);
|
||||
activityRefreshTask = null;
|
||||
}
|
||||
if (deviceRefreshHandler != null) {
|
||||
deviceRefreshHandler.removeCallbacks(alignRefreshTask);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -195,8 +198,15 @@ public class HomeFragment extends Fragment {
|
||||
public void onDpUpdate(String devId, Map<String, Object> dps) {
|
||||
android.util.Log.i("HomeFragment", "DP回调[" + devId + "] dps=" + dps);
|
||||
if (rvDevices != null) {
|
||||
if (isSelfInitiatedControl(devId)) {
|
||||
// 用户刚点了这张卡片的开关:胶囊已按乐观值显示,
|
||||
// 此时整表重载会让"常用设备"整块闪一下(顺序已稳定,但仍有重绘)。
|
||||
// 推迟到窗口结束后再对齐一次即可。
|
||||
scheduleDelayedAlignRefresh();
|
||||
} else {
|
||||
rvDevices.post(HomeFragment.this::scheduleDeviceRefresh);
|
||||
}
|
||||
}
|
||||
// DP 变化统一上报后端(source=panel):
|
||||
// 这是蓝牙 Mesh 本地通道控制的唯一上报路径——手机蓝牙直连 publishDps
|
||||
// 的 DP 回调只走 TuyaSdkService.notifyDpUpdate(不走设备级
|
||||
@ -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<String> lastDeviceOrder = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 按上次顺序重排设备列表。
|
||||
*
|
||||
* <p>后端已对 member 视角改为按 id 升序(位置固定),这里再加一层会话内兜底:
|
||||
* 万一后端顺序变化(如新设备插入、后台排序调整),也不让已在屏上的设备跳位。</p>
|
||||
*/
|
||||
private void applyStableOrder(List<TuyaDevice> 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<String, TuyaDevice> remain = new java.util.LinkedHashMap<>();
|
||||
for (TuyaDevice d : devices) {
|
||||
remain.put(d.getId(), d);
|
||||
}
|
||||
List<TuyaDevice> 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);
|
||||
}
|
||||
|
||||
@ -52,6 +52,21 @@ public class HomeDeviceAdapter extends RecyclerView.Adapter<HomeDeviceAdapter.Vi
|
||||
void onLongClick(TuyaDevice device, int position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制发起回调
|
||||
*
|
||||
* <p>用户在卡片上点了开关时通知页面,便于页面推迟整表刷新(避免列表重排/闪动)。</p>
|
||||
*/
|
||||
public interface OnControlStart {
|
||||
void onControlStart(String deviceId);
|
||||
}
|
||||
|
||||
private OnControlStart controlStartListener;
|
||||
|
||||
public void setOnControlStartListener(OnControlStart l) {
|
||||
this.controlStartListener = l;
|
||||
}
|
||||
|
||||
public HomeDeviceAdapter(List<TuyaDevice> devices, OnDeviceClick listener) {
|
||||
this.devices = devices;
|
||||
this.listener = listener;
|
||||
@ -215,6 +230,10 @@ public class HomeDeviceAdapter extends RecyclerView.Adapter<HomeDeviceAdapter.Vi
|
||||
holder.pill.setOnUserToggleListener(checked -> {
|
||||
// 锁定乐观值:下发结果回传前,忽略列表刷新带回来的旧状态
|
||||
lockPendingToggle(device.getId(), checked);
|
||||
// 通知页面:用户刚点了开关,别急着整表刷新(会闪动/跳位)
|
||||
if (controlStartListener != null) {
|
||||
controlStartListener.onControlStart(device.getId());
|
||||
}
|
||||
Map<String, Object> commands = new HashMap<>();
|
||||
commands.put(switchDpId, checked);
|
||||
PlatformServiceManager.getInstance(holder.itemView.getContext())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user