164 lines
4.4 KiB
C#
164 lines
4.4 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using UnityEngine.UI;
|
||
|
||
public class KeyboardScrollView : MonoBehaviour
|
||
{
|
||
[Header("Scroll View 组件")]
|
||
public ScrollRect scrollRect;
|
||
public RectTransform content;
|
||
|
||
[Header("Item 列表")]
|
||
private List<GameObject> items = new List<GameObject>();
|
||
|
||
private int currentIndex = 0;
|
||
|
||
public UnityEvent<string> enterEvent;
|
||
|
||
void Start()
|
||
{
|
||
// 自动收集 Content 下的所有直接子物体作为 item
|
||
items.Clear();
|
||
foreach (Transform child in content)
|
||
{
|
||
items.Add(child.gameObject);
|
||
}
|
||
|
||
// 禁止鼠标滚轮
|
||
scrollRect.scrollSensitivity = 0;
|
||
|
||
// 禁止鼠标拖动
|
||
CanvasGroup cg = scrollRect.GetComponent<CanvasGroup>();
|
||
if (cg == null) cg = scrollRect.gameObject.AddComponent<CanvasGroup>();
|
||
cg.interactable = false;
|
||
|
||
// 初始化:关闭所有 select,打开第一个
|
||
foreach (GameObject item in items)
|
||
{
|
||
Transform select = item.transform.GetChild(0);
|
||
if (select != null)
|
||
select.gameObject.SetActive(false);
|
||
}
|
||
|
||
currentIndex = 0;
|
||
currectSelect = items[0];
|
||
UpdateSelection();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (Input.GetKeyDown(KeyCode.DownArrow))
|
||
{
|
||
MoveNext();
|
||
}
|
||
else if (Input.GetKeyDown(KeyCode.UpArrow))
|
||
{
|
||
MovePrevious();
|
||
}
|
||
|
||
if (Input.GetKeyDown(KeyCode.Return))
|
||
{
|
||
if (currectSelect.name.Split('-').Length == 1)
|
||
return;
|
||
enterEvent?.Invoke(currectSelect.name.Split('-')[1]); //有些按钮会通向同一个页面
|
||
}
|
||
}
|
||
|
||
void MoveNext()
|
||
{
|
||
if (currentIndex < items.Count - 1)
|
||
{
|
||
currentIndex++;
|
||
UpdateSelection();
|
||
ScrollIfNeeded();
|
||
currectSelect = items[currentIndex];
|
||
}
|
||
}
|
||
|
||
void MovePrevious()
|
||
{
|
||
if (currentIndex > 0)
|
||
{
|
||
currentIndex--;
|
||
UpdateSelection();
|
||
ScrollIfNeeded();
|
||
currectSelect = items[currentIndex];
|
||
|
||
}
|
||
}
|
||
|
||
private GameObject currectSelect;
|
||
void UpdateSelection()
|
||
{
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
Transform select = items[i].transform.GetChild(0);
|
||
if (select != null)
|
||
{
|
||
select.gameObject.SetActive(i == currentIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
void ScrollIfNeeded()
|
||
{
|
||
// 强制刷新布局
|
||
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
|
||
|
||
RectTransform selectedItem = items[currentIndex].GetComponent<RectTransform>();
|
||
|
||
// 将 item 的四个角转换到 viewport 坐标系
|
||
Vector3[] itemWorldCorners = new Vector3[4];
|
||
selectedItem.GetWorldCorners(itemWorldCorners);
|
||
|
||
Vector3[] viewportWorldCorners = new Vector3[4];
|
||
scrollRect.viewport.GetWorldCorners(viewportWorldCorners);
|
||
|
||
float itemTop = itemWorldCorners[1].y; // 左上角 y
|
||
float itemBottom = itemWorldCorners[0].y; // 左下角 y
|
||
|
||
float viewportTop = viewportWorldCorners[1].y;
|
||
float viewportBottom = viewportWorldCorners[0].y;
|
||
|
||
float delta = 0f;
|
||
|
||
// item 顶部高于 viewport 顶部,向下滚动
|
||
if (itemTop > viewportTop)
|
||
{
|
||
delta = itemTop - viewportTop;
|
||
content.anchoredPosition -= new Vector2(0, delta);
|
||
}
|
||
// item 底部低于 viewport 底部,向上滚动
|
||
else if (itemBottom < viewportBottom)
|
||
{
|
||
delta = viewportBottom - itemBottom;
|
||
content.anchoredPosition += new Vector2(0, delta);
|
||
}
|
||
|
||
// 限制滚动范围
|
||
float maxY = Mathf.Max(0, content.rect.height - scrollRect.viewport.rect.height);
|
||
float y = Mathf.Clamp(content.anchoredPosition.y, 0, maxY);
|
||
content.anchoredPosition = new Vector2(content.anchoredPosition.x, y);
|
||
}
|
||
|
||
// 获取当前可视区域的第一个 item 索引
|
||
int GetFirstVisibleIndex()
|
||
{
|
||
float scrollTop = content.anchoredPosition.y;
|
||
|
||
for (int i = 0; i < items.Count; i++)
|
||
{
|
||
RectTransform item = items[i].GetComponent<RectTransform>();
|
||
float itemTop = Mathf.Abs(item.anchoredPosition.y);
|
||
float itemBottom = itemTop + item.rect.height;
|
||
|
||
if (itemBottom > scrollTop)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
}
|