Unity之ScrollRect简易实现

资料

ScrollRect源码

创建Scroll View,选择组件对象,查看源码

作用

实现滑动窗口,学习滑动窗口如何实现。

思路

  1. 继承IDragHandler, IBeginDragHandler, IEndDragHandler,实现拖拽功能
  2. 计算可视区域和移动区域的Bounds,限制移动区域

实现

主要方法:

  1. UpdateBounds方法用于计算可视区域和内容区域的包围盒
  2. AdjuseBounds方法调节内容区域的包围盒
  3. CalculateOffset方法计算偏移位置,防止超出边界
csharp 复制代码
using UnityEngine;
using UnityEngine.EventSystems;

public class SimpleScrollRect : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    [Header("拖动区域")]
    [SerializeField] RectTransform viewRect;

    [Header("移动内容")]
    [SerializeField] RectTransform content;

    [Header("水平移动")]
    [SerializeField] bool horizontal;

    [Header("竖直移动")]
    [SerializeField] bool vertical;

    Vector2 startLocalCursor;
    Vector2 contentStartPosition;
    bool isDrag = false;

    Bounds viewBounds;
    Bounds contentBounds;

    public void OnBeginDrag(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect,
        eventData.position, eventData.pressEventCamera,
        out startLocalCursor);
        contentStartPosition = content.anchoredPosition;
        isDrag = true;
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (isDrag == false) return;
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        Vector2 localCursor;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect,
        eventData.position, eventData.pressEventCamera, out localCursor))
            return;

        UpdateBounds();

        //从拖拽开始到当前帧,鼠标在 viewRect 局部坐标系中的总累计位移
        var pointerDelta = localCursor - startLocalCursor;
        //目标位置
        var position = contentStartPosition + pointerDelta;
        //从当前实际位置到目标位置所需的位移
        var delta = position - content.anchoredPosition;
        //边界限制
        var offset = CalculateOffset(ref viewBounds, ref contentBounds, horizontal, vertical, delta);
        position += offset;
        //水平或竖直移动限制
        if (!horizontal)
            position.x = content.anchoredPosition.x;
        if (!vertical)
            position.y = content.anchoredPosition.y;
        //更新位置
        if (position != content.anchoredPosition)
            content.anchoredPosition = position;
    }

    private readonly Vector3[] contentCorners = new Vector3[4];
    protected void UpdateBounds()
    {
        //生成可视区域bounds
        viewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);

        //生成内容区域Bounds,坐标系统一在viewRect的局部坐标系中
        var viewWorldToLocalMatrix = viewRect.worldToLocalMatrix;
        content.GetWorldCorners(contentCorners);
        contentBounds = GetBounds(contentCorners, ref viewWorldToLocalMatrix);

        //调整内容区域Bounds,确保内容区域永远不会小于可视区域
        Vector3 contentSize = contentBounds.size;
        Vector3 contentPos = contentBounds.center;
        var contentPivot = content.pivot;
        AdjustBounds(ref viewBounds, ref contentPivot, ref contentSize, ref contentPos);
        contentBounds.size = contentSize;
        contentBounds.center = contentPos;
    }
    private Bounds GetBounds(Vector3[] corners, ref Matrix4x4 viewWorldToLocalMatrix)
    {
        var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
        var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

        for (int j = 0; j < 4; j++)
        {
            Vector3 v = viewWorldToLocalMatrix.MultiplyPoint3x4(corners[j]);
            vMin = Vector3.Min(v, vMin);
            vMax = Vector3.Max(v, vMax);
        }

        var bounds = new Bounds(vMin, Vector3.zero);
        bounds.Encapsulate(vMax);
        return bounds;
    }
    private void AdjustBounds(ref Bounds viewBounds, ref Vector2 contentPivot, ref Vector3 contentSize, ref Vector3 contentPos)
    {
        Vector3 excess = viewBounds.size - contentSize;//可视区域超出内容区域的尺寸

        if (excess.x > 0)//当内容区域宽度小于可视区域宽度,扩展内容区域宽度
        {
            //contentPivot.x=0.5 原有矩形中心位置不变,宽度向两侧增加,中心点不改变
            //contentPivto.x=0 原有矩形左边界位置不变,宽度向右侧增加,中心点向右移动
            //contentPivot.x=1 原有矩形右边界位置不变,宽度向左测增加,中心点向左移动
            //宽度改变时,改变中心点,保证pivot的视觉位置不变
            contentPos.x -= excess.x * (contentPivot.x - 0.5f);
            contentSize.x = viewBounds.size.x;
        }
        if (excess.y > 0)//当内容区域高度小于可视区域高度,扩展内容区域高度
        {
            contentPos.y -= excess.y * (contentPivot.y - 0.5f);
            contentSize.y = viewBounds.size.y;
        }
    }
    private Vector2 CalculateOffset(ref Bounds viewBounds, ref Bounds contentBounds, bool horizontal, bool vertical, Vector2 delta)
    {
        Vector2 offset = Vector2.zero;
        Vector2 min = contentBounds.min;
        Vector2 max = contentBounds.max;
        if (horizontal)
        {
            min.x += delta.x;//模拟内容偏移后的左边界
            max.x += delta.x;//模拟内容偏移后的右边界

            float minOffset = viewBounds.min.x - min.x;//可视区域的左边界减去内容左边界
                                                       //>0 内容左边界 在  可视区域左边界 左侧
                                                       //<0 内容左边界 在  可视区域左边界 右侧

            float maxOffset = viewBounds.max.x - max.x;//可视区域的右边界减去内容右边界
                                                       //>0 内容右边界 在 可视区域右边界 左侧
                                                       //<0 内容右边界 在 可视区域右边界 右侧
            if (minOffset < -0.001f) //左边界对齐
                offset.x = minOffset;
            else if (maxOffset > 0.001f)//右边界对齐
                offset.x = maxOffset;
        }

        if (vertical)
        {
            min.y += delta.y;//模拟内容偏移后的下边界
            max.y += delta.y;//模拟内容偏移后的上边界

            float maxOffset = viewBounds.max.y - max.y;//可视区域的上边界减去内容上边界
                                                       //>0 内容上边界 在  可视区域上边界 下侧
                                                       //<0 内容上边界 在  可视区域上边界 上侧

            float minOffset = viewBounds.min.y - min.y;//可视区域的下边界减去内容下边界
                                                       //>0 内容下边界 在  可视区域下边界 下侧
                                                       //<0 内容下边界 在  可视区域下边界 上侧
            if (maxOffset > 0.001f)//上边界对齐
                offset.y = maxOffset;
            else if (minOffset < -0.001f)//下边界对齐
                offset.y = minOffset;
        }

        return offset;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        isDrag = false;
    }
}
相关推荐
不吃凉粉9 小时前
Unity与AS通信
unity·游戏引擎
两水先木示21 小时前
【Unity】探索小地图迷雾散开Shader效果(基础版)
unity·游戏引擎
WarrenMondeville1 天前
AI复刻街头霸王到 Unity
unity·游戏引擎
玖玥拾1 天前
Lua 基础语法(六)xLua Lua 调用 C# 交互
开发语言·unity·c#·lua
xcLeigh2 天前
Unity基础:MonoBehaviour的生命周期——Awake、OnEnable、Start、Update执行顺序
unity·游戏引擎
qq_213157892 天前
其域lcc2丢失部分lod/远处显示空洞的解决方式
unity·lcc2
辻弋2012 天前
自动检测硬件生成驱动清单、实时监控FPS、120秒性能记录——30+项优化模块化开关,所有改动可精确复原
服务器·windows·游戏引擎·电脑·开源软件
点心的游戏开发世界2 天前
GDScript 入门笔记:目录
开发语言·笔记·游戏引擎·godot
XR技术研习社2 天前
关于 PICO 串流测试中报错 IndexOutOfRangeException: renderPassIndex 的两个排障方法
unity·ar·xr·vr
想做后端的前端2 天前
Unity · 性能优化:内存管理完全指南:从托管堆到跨桥开销
unity·性能优化·游戏引擎