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);
|
activityRefreshHandler.removeCallbacks(activityRefreshTask);
|
||||||
activityRefreshTask = null;
|
activityRefreshTask = null;
|
||||||
}
|
}
|
||||||
|
if (deviceRefreshHandler != null) {
|
||||||
|
deviceRefreshHandler.removeCallbacks(alignRefreshTask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -195,7 +198,14 @@ public class HomeFragment extends Fragment {
|
|||||||
public void onDpUpdate(String devId, Map<String, Object> dps) {
|
public void onDpUpdate(String devId, Map<String, Object> dps) {
|
||||||
android.util.Log.i("HomeFragment", "DP回调[" + devId + "] dps=" + dps);
|
android.util.Log.i("HomeFragment", "DP回调[" + devId + "] dps=" + dps);
|
||||||
if (rvDevices != null) {
|
if (rvDevices != null) {
|
||||||
rvDevices.post(HomeFragment.this::scheduleDeviceRefresh);
|
if (isSelfInitiatedControl(devId)) {
|
||||||
|
// 用户刚点了这张卡片的开关:胶囊已按乐观值显示,
|
||||||
|
// 此时整表重载会让"常用设备"整块闪一下(顺序已稳定,但仍有重绘)。
|
||||||
|
// 推迟到窗口结束后再对齐一次即可。
|
||||||
|
scheduleDelayedAlignRefresh();
|
||||||
|
} else {
|
||||||
|
rvDevices.post(HomeFragment.this::scheduleDeviceRefresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// DP 变化统一上报后端(source=panel):
|
// DP 变化统一上报后端(source=panel):
|
||||||
// 这是蓝牙 Mesh 本地通道控制的唯一上报路径——手机蓝牙直连 publishDps
|
// 这是蓝牙 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) {
|
private void initViews(View root) {
|
||||||
rvDevices = root.findViewById(R.id.rv_devices);
|
rvDevices = root.findViewById(R.id.rv_devices);
|
||||||
progressBar = root.findViewById(R.id.progress_devices);
|
progressBar = root.findViewById(R.id.progress_devices);
|
||||||
@ -265,6 +308,11 @@ public class HomeFragment extends Fragment {
|
|||||||
adapter = new HomeDeviceAdapter(new ArrayList<>(), (device, position) ->
|
adapter = new HomeDeviceAdapter(new ArrayList<>(), (device, position) ->
|
||||||
openDevicePanel(device));
|
openDevicePanel(device));
|
||||||
adapter.setAccentColor(accent);
|
adapter.setAccentColor(accent);
|
||||||
|
// 用户点了卡片开关:记录下来,DP 回调时不做整表刷新(避免"常用设备"闪动/跳位)
|
||||||
|
adapter.setOnControlStartListener(deviceId -> {
|
||||||
|
pendingControlDeviceId = deviceId;
|
||||||
|
pendingControlAt = System.currentTimeMillis();
|
||||||
|
});
|
||||||
// 长按设备:编辑(重命名)/ 删除
|
// 长按设备:编辑(重命名)/ 删除
|
||||||
adapter.setOnDeviceLongClickListener((device, position) ->
|
adapter.setOnDeviceLongClickListener((device, position) ->
|
||||||
DeviceActionDialogs.showActions(requireActivity(),
|
DeviceActionDialogs.showActions(requireActivity(),
|
||||||
@ -410,6 +458,8 @@ public class HomeFragment extends Fragment {
|
|||||||
Toast.makeText(requireContext(),
|
Toast.makeText(requireContext(),
|
||||||
"已切换到「" + (home.getName() != null ? home.getName() : "家庭") + "」",
|
"已切换到「" + (home.getName() != null ? home.getName() : "家庭") + "」",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
|
// 换家庭:上一次的设备顺序不再适用,重置后按新家庭顺序展示
|
||||||
|
lastDeviceOrder.clear();
|
||||||
loadDevicesFromBackend();
|
loadDevicesFromBackend();
|
||||||
loadRecentActivities();
|
loadRecentActivities();
|
||||||
}
|
}
|
||||||
@ -1160,11 +1210,62 @@ public class HomeFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
result.add(td);
|
result.add(td);
|
||||||
}
|
}
|
||||||
// 在线设备排前面,离线排后面(组内保持原顺序)
|
// 1) 先按「上一次显示的顺序」重排:设备位置对用户是固定的,
|
||||||
|
// 不能因为一次控制上报就让常用设备换位置(JD2BTSA 跳到 JD6BTSA 前面)。
|
||||||
|
// 新增设备按后端返回顺序追加到末尾。
|
||||||
|
applyStableOrder(result);
|
||||||
|
// 2) 在线设备排前面,离线排后面(稳定排序,组内保持上一步的相对顺序)
|
||||||
result.sort((a, b) -> Boolean.compare(!a.isOnline(), !b.isOnline()));
|
result.sort((a, b) -> Boolean.compare(!a.isOnline(), !b.isOnline()));
|
||||||
return result;
|
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) {
|
private void showLoading(boolean show) {
|
||||||
progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
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);
|
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) {
|
public HomeDeviceAdapter(List<TuyaDevice> devices, OnDeviceClick listener) {
|
||||||
this.devices = devices;
|
this.devices = devices;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@ -215,6 +230,10 @@ public class HomeDeviceAdapter extends RecyclerView.Adapter<HomeDeviceAdapter.Vi
|
|||||||
holder.pill.setOnUserToggleListener(checked -> {
|
holder.pill.setOnUserToggleListener(checked -> {
|
||||||
// 锁定乐观值:下发结果回传前,忽略列表刷新带回来的旧状态
|
// 锁定乐观值:下发结果回传前,忽略列表刷新带回来的旧状态
|
||||||
lockPendingToggle(device.getId(), checked);
|
lockPendingToggle(device.getId(), checked);
|
||||||
|
// 通知页面:用户刚点了开关,别急着整表刷新(会闪动/跳位)
|
||||||
|
if (controlStartListener != null) {
|
||||||
|
controlStartListener.onControlStart(device.getId());
|
||||||
|
}
|
||||||
Map<String, Object> commands = new HashMap<>();
|
Map<String, Object> commands = new HashMap<>();
|
||||||
commands.put(switchDpId, checked);
|
commands.put(switchDpId, checked);
|
||||||
PlatformServiceManager.getInstance(holder.itemView.getContext())
|
PlatformServiceManager.getInstance(holder.itemView.getContext())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user