killapp/Assets/Scripts/UI/Pages/HomePage/HomePageDeviceItem.cs

245 lines
9.0 KiB
C#
Raw Normal View History

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);
}
}
}