unity笔记--ScrollRect的拖拽和回弹的实现

unity笔记–事件接口
Unity笔记–RectTransform的位置与轴心点、锚点、矩形框原点
Unity脚本文档–ScrollRect

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// 
/// 无滚动条,只拖拽不滚动,垂直方向,Elastic
/// 
public class TestScrollRect : UIBehaviour,IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, ICanvasElement, ILayoutElement, ILayoutGroup
{
    [NonSerialized] private RectTransform m_Rect;
    private RectTransform rectTransform
    {
        get
        {
            if (m_Rect == null)
                m_Rect = GetComponent<RectTransform>();
            return m_Rect;
        }
    }

    [SerializeField]
    private RectTransform m_Viewport;
    public RectTransform viewport { get => m_Viewport; }

    [SerializeField]
    private RectTransform m_Content;
    public RectTransform content
    {
        get => m_Content;
        set => m_Content = value;
    }


    private Bounds m_ContentBounds;
    private Bounds m_ViewBounds;


    /// 
    /// 鼠标点相对于viewport的坐标
    /// 
    private Vector2 m_PointerStartLocalCursor = Vector2.zero;

    /// 
    /// 开始拖拽时content相对于锚点的位置
    /// 
    protected Vector2 m_ContentStartPosition = Vector2.zero;

    private bool m_Dragging;
    [NonSerialized]
    private bool m_HasRebuiltLayout = false;

    protected override void Start()
    {
        LayoutRebuilder.ForceRebuildLayoutImmediate(content);//添加,修正运行时content初始高度为0
    }

    public void OnInitializePotentialDrag(PointerEventData eventData)
    {
        //拖拽时的初始化
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        //开始拖拽
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        if (!IsActive())
            return;

        UpdateBounds();

        m_PointerStartLocalCursor = Vector2.zero;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor);
        m_ContentStartPosition = m_Content.anchoredPosition; //点击时content相对于锚点的坐标

        Debug.Log("m_PointerStartLocalCursor = " + m_PointerStartLocalCursor);
        Debug.Log("m_ContentStartPosition  = " + m_ContentStartPosition);

        m_Dragging = true;
    }

    /// 
    /// Calculate the bounds the ScrollRect should be using.
    /// 
    protected void UpdateBounds()
    {
        Debug.Log("UpdateBounds:");

        m_ViewBounds = new Bounds(viewport.rect.center, viewport.rect.size);

        m_ContentBounds = GetBounds();  //获取viewport坐标系下content框
        
        if (m_Content == null)
            return;

        Debug.Log($"m_ViewBounds.size = {m_ViewBounds.size}, m_ViewBounds.center = {m_ViewBounds.center}");
        Debug.Log($"m_ContentBounds.size = {m_ContentBounds.size}, m_ContentBounds.center = {m_ContentBounds.center}");
    }

    private readonly Vector3[] m_Corners = new Vector3[4];
    private Bounds GetBounds()
    {
        if (m_Content == null)
            return new Bounds();

        var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
        var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

        var toLocal = viewport.worldToLocalMatrix;  
        m_Content.GetWorldCorners(m_Corners);
        for (var j = 0; j < 4; j++)  //将content顶点坐标转到viewport坐标系上
        {
            var v = toLocal.MultiplyPoint3x4(m_Corners[j]);
            vMin = Vector3.Min(v, vMin);
            vMax = Vector3.Max(v, vMax);
        }

        var bounds = new Bounds(vMin, Vector3.zero);
        bounds.Encapsulate(vMax);
        return bounds;
    }


    public void OnDrag(PointerEventData eventData)
    {   //拖拽中
        Debug.Log("OnDrag:");

        if (!m_Dragging)
            return;

        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        if (!IsActive())
            return;

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

        UpdateBounds();

        var pointerDelta = localCursor - m_PointerStartLocalCursor;  //鼠标移动量
        Debug.Log("pointerDelta = " + pointerDelta);

        Vector2 position = m_ContentStartPosition + pointerDelta;  //Content移动后的位置(相对于锚点)
        Debug.Log("position = m_ContentStartPosition + pointerDelta = " + position);

        // content移动后脱离viewport的偏移量
        Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition);  //delta:content移动后的位置-当前的位置 = content应该移动的距离
        Debug.Log($"delta = m_ContentStartPosition{m_ContentStartPosition} - m_Content.anchoredPosition{m_Content.anchoredPosition} + pointerDelta");
        Debug.Log($"delta = position{position} - m_Content.anchoredPosition{m_Content.anchoredPosition} = {position - m_Content.anchoredPosition}");

        Debug.Log("offset = " + offset);

        position += offset;  //调整content移动后的位置,使其最高点不低于viewport顶 或 最高点高于viewport顶时最低点不高于viewport底

        Debug.Log("position + offset = " + position);

        if (offset.y != 0)
            position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y);  //根据拖拽的距离确定弹性拉伸的长度,决定content移动后的位置

        Debug.Log("position - RubberDelta = " + position);

        SetContentAnchoredPosition(position);  //移动content
    }


    public void OnEndDrag(PointerEventData eventData)
    {
        //拖拽结束
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        m_Dragging = false;
    }


    public void Rebuild(CanvasUpdate executing)
    {
        //网格重建
        if (executing == CanvasUpdate.Prelayout)
        {
            // UpdateCachedData();
        }

        if (executing == CanvasUpdate.PostLayout)
        {
            // UpdateBounds();
            // UpdateScrollbars(Vector2.zero);
            // UpdatePrevData();

            m_HasRebuiltLayout = true;
        }
    }

    public void LayoutComplete()
    {
        //布局重建后

    }

    public void GraphicUpdateComplete()
    {
        //图形重建后,ScrollRect只用参与布局
    }

    public void CalculateLayoutInputHorizontal()
    {
        //重写:minWidth、preferredWidth 和 flexibleWidth
    }

    public void CalculateLayoutInputVertical()
    {
        //重写:minHeight、preferredHeight 和 flexibleHeight 
    }

    public float minWidth { get; }
    public float preferredWidth { get; }
    public float flexibleWidth { get; }
    public float minHeight { get; }
    public float preferredHeight { get; }
    public float flexibleHeight { get; }
    public int layoutPriority { get; }

    public void SetLayoutHorizontal()
    {
        //ILayoutGroup接口,布局系统会首先调用 SetLayoutHorizontal,然后调用 SetLayoutVertical。
    }

    public void SetLayoutVertical()
    {
    }

    private Vector2 CalculateOffset(Vector2 delta)  //delta:content应该移动的距离
    {
        Vector2 offset = Vector2.zero;

        Vector2 min = m_ContentBounds.min;//左下角
        Vector2 max = m_ContentBounds.max;//右上角

        Debug.Log($"m_ContentBounds.min = {min.y}, m_ContentBounds.max = {max.y}");
        Debug.Log($"m_ViewBounds.min = {m_ViewBounds.min.y }, m_ViewBounds.max = {m_ViewBounds.max.y }");

        min.y += delta.y;
        max.y += delta.y;

        float maxOffset = m_ViewBounds.max.y - max.y;  //content移动后的右上角距viewport右上角的距离
        float minOffset = m_ViewBounds.min.y - min.y;  //content移动后的左下角距viewport左下角的距离

        Debug.Log($"maxOffset = m_ViewBounds.max.y: {m_ViewBounds.max.y} - max.y: {max.y} = {maxOffset}");
        Debug.Log($"minOffset = m_ViewBounds.min.y: {m_ViewBounds.min.y} - min.y: {min.y} = {minOffset}");

        if (maxOffset > 0.001f || m_ViewBounds.size.y > m_ContentBounds.size.y)  //content不到顶或content小于viewport。更改,原为:maxOffset > 0.001f
            offset.y = maxOffset;
        else if (minOffset < -0.001f)
            offset.y = minOffset;

        Debug.Log("offset = " + offset);
        return offset;
    }

    private static float RubberDelta(float overStretching, float viewSize)
    {
        return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
    }

    protected virtual void SetContentAnchoredPosition(Vector2 position)
    {
        position.x = m_Content.anchoredPosition.x;

        if (position != m_Content.anchoredPosition)
        {
            Debug.Log("Set Content AnchoredPosition = " + position);
            m_Content.anchoredPosition = position;
            UpdateBounds();
        }
    }

    protected virtual void LateUpdate()
    {
        if (!m_Content)
            return;

        EnsureLayoutHasRebuilt();

        UpdateBounds();

        Vector2 offset = CalculateOffset(Vector2.zero);  //当前content(不移动)脱离viewport的偏移量

        if (!m_Dragging && (offset != Vector2.zero))
        {
            Vector2 position = m_Content.anchoredPosition;
            position += offset;  //调整content移动后的位置,使其最高点不低于viewport顶 或 最高点高于viewport顶时最低点不高于viewport底

            Debug.Log("position = " + position);
            SetContentAnchoredPosition(position);  //移动content弹回viewport里
        }
    }

    private void EnsureLayoutHasRebuilt()
    {
        if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
            Canvas.ForceUpdateCanvases();
    }
}

你可能感兴趣的:(unity笔记,unity)