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();
}
}