本次提交包含以下核心更新: 1. 新增设备置顶功能,支持将设备在列表中优先展示 2. 实现设备重命名、分享、取消共享功能 3. 优化首页设备列表滑动交互,重构设备项UI菜单逻辑 4. 新增is_top设备字段并完善设备列表排序逻辑 5. 调整部分UI布局与服务器接口地址 6. 补充多语言文案与新增提示文本 7. 修复绑定设备错误提示适配逻辑
245 lines
9.0 KiB
C#
245 lines
9.0 KiB
C#
using System.Collections.Generic;
|
||
using DG.Tweening;
|
||
using Kill.Managers;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace Kill.UI.Pages
|
||
{
|
||
/// <summary>
|
||
/// 首页设备列表项 - 显示单个设备信息
|
||
/// 支持左滑手势:左滑时内容元素向左滑出,露出右侧菜单,
|
||
/// 滑动超过菜单宽度1/3松手则完整展开,否则缩回
|
||
/// </summary>
|
||
public class HomePageDeviceItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||
{
|
||
public Text deviceNameText;
|
||
/// <summary>
|
||
/// 置顶标识
|
||
/// </summary>
|
||
public GameObject topIcon;
|
||
public GameObject selectedIndicator;
|
||
/// <summary>菜单(子物体,锚点位于item最右侧),内容左移后露出</summary>
|
||
public RectTransform menuRect;
|
||
|
||
// 当前设备信息
|
||
public DeviceInfo deviceInfo;
|
||
/// <summary>
|
||
/// 菜单按钮数组 0:重命名 1:分享 2:删除 3:置顶 4:取消置顶
|
||
/// 最多显示3个按钮 拥有的设备显示分享 被分享的设备显示删除
|
||
/// 根据置顶状态显示置顶/取消置顶按钮
|
||
/// </summary>
|
||
public Button[] menuButtons;
|
||
/// <summary>当前展开菜单的item(全局仅允许一个菜单展开)</summary>
|
||
private static HomePageDeviceItem openItem;
|
||
|
||
private float menuWidth;
|
||
private float currentOffset;
|
||
private float dragStartOffset;
|
||
private bool isMenuOpen;
|
||
private bool isDragging;
|
||
private Vector3 menuInitPos;
|
||
private Tweener tweener;
|
||
private Button button;
|
||
private bool menuListenersBound;
|
||
/// <summary>除菜单外的内容子物体</summary>
|
||
private readonly List<RectTransform> contentRects = new List<RectTransform>();
|
||
/// <summary>各内容子物体的初始位置</summary>
|
||
private readonly List<Vector2> contentInitPositions = new List<Vector2>();
|
||
|
||
void Awake()
|
||
{
|
||
button = GetComponent<Button>();
|
||
// 点击item主体:关闭其他item展开的菜单,同时收起自己的
|
||
if (button != null)
|
||
button.onClick.AddListener(OnItemClicked);
|
||
InitSwipe();
|
||
}
|
||
|
||
/// <summary>
|
||
/// item主体点击:全局仅保留一个菜单,点击任意item时关闭其他item的展开菜单
|
||
/// </summary>
|
||
private void OnItemClicked()
|
||
{
|
||
if (openItem != null && openItem != this)
|
||
openItem.CloseMenu();
|
||
CloseMenu();
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
// 防止拖拽中被禁用且未恢复的情况
|
||
if (button != null) button.interactable = true;
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
// 销毁时释放全局展开引用
|
||
if (openItem == this)
|
||
openItem = null;
|
||
}
|
||
|
||
public void InitDeviceItem(DeviceInfo device, bool isOwner)
|
||
{
|
||
deviceInfo = device;
|
||
deviceNameText.text = device.device_name;
|
||
selectedIndicator.SetActive(false);
|
||
if (topIcon != null) topIcon.SetActive(device.is_top);
|
||
SetupMenuButtons(isOwner);
|
||
BindMenuButtonClose();
|
||
InitSwipe();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据设备归属和置顶状态显示/隐藏菜单按钮
|
||
/// 0重命名 1分享 2删除 3置顶 4取消置顶
|
||
/// 拥有者:重命名+分享+置顶/取消置顶;被分享者:删除+置顶/取消置顶
|
||
/// </summary>
|
||
private void SetupMenuButtons(bool isOwner)
|
||
{
|
||
if (menuButtons == null || menuButtons.Length < 5) return;
|
||
menuButtons[0].gameObject.SetActive(true); // 重命名仅拥有者
|
||
menuButtons[1].gameObject.SetActive(isOwner); // 分享仅拥有者
|
||
menuButtons[2].gameObject.SetActive(!isOwner); // 删除(移除共享)仅被分享者
|
||
menuButtons[3].gameObject.SetActive(!deviceInfo.is_top); // 置顶:未置顶时显示
|
||
menuButtons[4].gameObject.SetActive(deviceInfo.is_top); // 取消置顶:已置顶时显示
|
||
}
|
||
|
||
/// <summary>
|
||
/// 点击菜单内任意按钮时自动收起菜单
|
||
/// </summary>
|
||
private void BindMenuButtonClose()
|
||
{
|
||
if (menuListenersBound || menuRect == null) return;
|
||
menuListenersBound = true;
|
||
foreach (var b in menuRect.GetComponentsInChildren<Button>(true))
|
||
b.onClick.AddListener(CloseMenu);
|
||
}
|
||
|
||
public void SetSelectedState(bool isSelected)
|
||
{
|
||
// 设置选中状态
|
||
selectedIndicator.SetActive(isSelected);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化滑动数据:收集内容子物体、记录初始位置,重置为收起状态
|
||
/// </summary>
|
||
private void InitSwipe()
|
||
{
|
||
tweener?.Kill();
|
||
if (menuRect == null) return;
|
||
// 先复位已记录的内容,再重新收集(兼容item复用)
|
||
ApplyOffset(0);
|
||
contentRects.Clear();
|
||
contentInitPositions.Clear();
|
||
foreach (Transform child in transform)
|
||
{
|
||
if (child == menuRect) continue;
|
||
RectTransform rt = child as RectTransform;
|
||
if (rt == null) continue;
|
||
contentRects.Add(rt);
|
||
contentInitPositions.Add(rt.anchoredPosition);
|
||
}
|
||
menuWidth = menuRect.sizeDelta.x;
|
||
menuInitPos = menuRect.anchoredPosition3D;
|
||
isMenuOpen = false;
|
||
currentOffset = 0;
|
||
ApplyOffset(0);
|
||
}
|
||
|
||
public void OnBeginDrag(PointerEventData eventData)
|
||
{
|
||
if (menuRect == null || menuWidth <= 0) return;
|
||
// 拖拽滑动时禁用按钮点击,避免与菜单滑动同时触发
|
||
if (button != null) button.interactable = false;
|
||
// 其他item的菜单展开时,先收起,保证全局只有一个菜单
|
||
if (openItem != null && openItem != this)
|
||
openItem.CloseMenu();
|
||
isDragging = true;
|
||
currentOffset = isMenuOpen ? -menuWidth : 0;
|
||
dragStartOffset = currentOffset;
|
||
ApplyOffset(currentOffset);
|
||
}
|
||
|
||
public void OnDrag(PointerEventData eventData)
|
||
{
|
||
if (!isDragging) return;
|
||
// 累加位移并限制在 [-菜单宽度, 0],只允许左滑露出菜单
|
||
currentOffset += eventData.delta.x;
|
||
currentOffset = Mathf.Clamp(currentOffset, -menuWidth, 0);
|
||
ApplyOffset(currentOffset);
|
||
}
|
||
|
||
public void OnEndDrag(PointerEventData eventData)
|
||
{
|
||
if (!isDragging) return;
|
||
isDragging = false;
|
||
// 恢复按钮点击
|
||
if (button != null) button.interactable = true;
|
||
// 右滑超过菜单宽度的1/5则直接缩回(方便关闭已展开的菜单)
|
||
float dragDistance = currentOffset - dragStartOffset;
|
||
if (dragDistance > menuWidth / 5f)
|
||
{
|
||
AnimateTo(0);
|
||
return;
|
||
}
|
||
// 左滑展示超过菜单宽度的1/3则完整滑出,否则缩回
|
||
float target = currentOffset < -menuWidth / 3f ? -menuWidth : 0;
|
||
AnimateTo(target);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收起菜单(供菜单按钮点击等外部调用)
|
||
/// </summary>
|
||
public void CloseMenu()
|
||
{
|
||
if (!isMenuOpen) return;
|
||
AnimateTo(0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将内容子物体和菜单一起平移到指定偏移(内容左移、菜单从屏幕外滑入露出)
|
||
/// </summary>
|
||
private void ApplyOffset(float offset)
|
||
{
|
||
for (int i = 0; i < contentRects.Count; i++)
|
||
{
|
||
Vector2 pos = contentInitPositions[i];
|
||
pos.x += offset;
|
||
contentRects[i].anchoredPosition = pos;
|
||
}
|
||
Vector3 menuPos = menuInitPos;
|
||
menuPos.x += offset;
|
||
menuRect.anchoredPosition3D = menuPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平滑动画到目标偏移
|
||
/// </summary>
|
||
private void AnimateTo(float target)
|
||
{
|
||
tweener?.Kill();
|
||
bool open = target < 0;
|
||
if (open)
|
||
{
|
||
// 已有其他item的菜单展开时,先收起,保证全局只有一个菜单
|
||
if (openItem != null && openItem != this)
|
||
openItem.CloseMenu();
|
||
openItem = this;
|
||
}
|
||
else if (openItem == this)
|
||
{
|
||
openItem = null;
|
||
}
|
||
isMenuOpen = open;
|
||
tweener = DOTween.To(() => currentOffset, v =>
|
||
{
|
||
currentOffset = v;
|
||
ApplyOffset(v);
|
||
}, target, 0.25f).SetEase(Ease.OutCubic);
|
||
}
|
||
}
|
||
}
|