先看效果:
当选中到显示范围外的UI时,Scroll View的content会自动往下翻或者往上翻 。
在进行游戏UI的手柄和键盘操作适配时,可以使用UGUI的Navigation功能,我们可以通过方向键来切换UI选中。
![](https://i-blog.csdnimg.cn/direct/c7079ea97e8e495da361cb6724d413d8.png)
但是在使用Scroll View时,如果切换到超出ScrollView显示范围之外的UI就看不到了。
如图:
需要自己进行一下适配,话不多说,上代码:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ScrollViewSelectControl : MonoBehaviour
{
public ScrollRect scrollRect;
public RectTransform content;
void Start()
{
// 为每个子UI元素添加EventTrigger组件
foreach (Transform child in content)
{
EventTrigger trigger = child.gameObject.AddComponent<EventTrigger>();
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.Select;
entry.callback.AddListener((eventData) => { EnsureVisible((RectTransform)child); });
trigger.triggers.Add(entry);
}
}
void EnsureVisible(RectTransform target)
{
// 获取Scroll View的显示区域
RectTransform viewport = scrollRect.viewport;
// 将目标UI元素的坐标转换到viewport的局部坐标系中
Vector3[] targetCorners = new Vector3[4];
target.GetWorldCorners(targetCorners);
Vector3[] viewportCorners = new Vector3[4];
viewport.GetWorldCorners(viewportCorners);
// 计算目标UI元素相对于viewport的偏移量
float offsetMin = targetCorners[0].y - viewportCorners[0].y;
float offsetMax = targetCorners[2].y - viewportCorners[2].y;
// 调整Scroll View的content位置
if (offsetMin < 0)
{
scrollRect.content.anchoredPosition += new Vector2(0, -offsetMin);
}
else if (offsetMax > 0)
{
scrollRect.content.anchoredPosition += new Vector2(0, -offsetMax);
}
}
}
这里注意GetWorldCorners这个函数,它的作用是获取一个RectTransform 的四个角的世界坐标,需要传入的是一个至少长度为四的数组,因为数组是引用类型,在函数内执行的时候会为数组赋值,所以我们创建两个数组来获取RectTransform四个角的坐标。关于GetWorldCorners的详情可以看这里。在获取我们选中的UI的四个角的世界坐标和ScrollView的显示区域的四个角坐标后,通过将对应的坐标相减就可以得到偏移量了,因为我这个ScrollView是只能上下滑动的,所以就比对数组中的02(RectTransform的左下角和右上角)元素的y值,如果要左右滑动就换成比对x值就好了。
--------------------------------------------------------------分割线--------------------------------------------------------
发现原来的功能有的不全,那么现在来做一个进阶版本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ScrollViewController : MonoBehaviour
{
public ScrollRect scrollRect;
public RectTransform content;
public bool horizontal = true;
public bool vertical = true;
public Canvas canvas;
private void Awake() {
canvas = GetComponentInParent<Canvas>();
}
private void Start() {
UpdateItem();
}
public void UpdateItem()
{
// 为每个子UI元素添加EventTrigger组件
foreach (Transform child in content)
{
EventTrigger trigger = child.gameObject.AddComponent<EventTrigger>();
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.Select;
entry.callback.AddListener((eventData) => { EnsureVisible((RectTransform)child); });
trigger.triggers.Add(entry);
}
}
void EnsureVisible(RectTransform target)
{
// 获取Scroll View的显示区域
RectTransform viewport = scrollRect.viewport;
// 将目标UI元素的坐标转换到viewport的局部坐标系中
Vector3[] targetCorners = new Vector3[4];
target.GetWorldCorners(targetCorners);
Vector3[] viewportCorners = new Vector3[4];
viewport.GetWorldCorners(viewportCorners);
// 将屏幕坐标转换为Canvas内的局部坐标
Vector2 localTargetMin, localTargetMax, localViewportMin, localViewportMax;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, targetCorners[0]), canvas.worldCamera, out localTargetMin);
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, targetCorners[2]), canvas.worldCamera, out localTargetMax);
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, viewportCorners[0]), canvas.worldCamera, out localViewportMin);
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, viewportCorners[2]), canvas.worldCamera, out localViewportMax);
// 计算目标UI元素相对于viewport的偏移量
Vector2 offsetMin = localTargetMin - localViewportMin;
Vector2 offsetMax = localTargetMax - localViewportMax;
// 调整Scroll View的content位置
if (vertical)
{
if (offsetMin.y < 0)
scrollRect.content.anchoredPosition += new Vector2(0, -offsetMin.y);
else if (offsetMax.y > 0)
scrollRect.content.anchoredPosition += new Vector2(0, -offsetMax.y);
}
if (horizontal)
{
if (offsetMin.x < 0)
scrollRect.content.anchoredPosition += new Vector2(-offsetMin.x, 0);
else if (offsetMax.x > 0)
scrollRect.content.anchoredPosition += new Vector2(-offsetMax.x, 0);
}
}
}
现在我们通过设置 ScrollViewSelectedControl 类中的horizontal和vertical两个布尔值就可以控制水平和垂直方向的滑动了。此外,还添加了将获取到的世界坐标都转换为canvas的局部坐标,防止因为canvas缩放带来的错位。
最后让我们看看效果吧。