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;
    }
}
相关推荐
WarrenMondeville3 小时前
9.Unity面向对象-对象池
unity
KaGme9 小时前
生成3DGS场景在unity中的呈现
3d·unity·游戏引擎
zyh______19 小时前
关于unity的序列化
unity·游戏引擎
weixin_4093831220 小时前
godot碰撞测试的学习
学习·游戏引擎·godot
电子云与长程纠缠20 小时前
Godot学习06 - AnimationPlayer内置动画
学习·游戏引擎·godot
星夜泊客21 小时前
C# : 引用类型都存在堆上吗
unity·c#
点量云实时渲染-小芹1 天前
Unity模型数字孪生虚拟仿真webgl推流卡实时云渲染推流
unity·webgl·数字孪生·实时云渲染·虚拟仿真·云推流
mxwin1 天前
Unity Shader 齐次坐标与透视除法理解 SV_POSITION 的 w 分量
unity·游戏引擎·shader
NPUQS1 天前
【Unity 3D学习】Unity 与 Python 互通入门:点击按钮调用 Python(超简单示例)
学习·3d·unity