ro-webgl/Assets/Src/Core/UI/ScrollRect/LoopScrollRect.cs
2021-12-21 09:40:39 +08:00

1474 lines
52 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System;
using System.Collections.Generic;
using LuaInterface;
namespace UnityEngine.UI
{
//Lua中调用的回调函数
public delegate void LuaUpdateCellCallback(LuaTable self, GameObject go, int dataIndex);
[AddComponentMenu("")]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public abstract class LoopScrollRect : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
{
/// <summary>
/// 更新Cell中的回调函数
/// </summary>
/// <param name="go"></param>
/// <param name="dataIndex"></param>
public delegate void UpdateCellCallback(GameObject go,int dataIndex);
//==========LoopScrollRect==========
[HideInInspector]
public int totalCount; //negative means INFINITE mode
[HideInInspector]
[NonSerialized]
public object[] objectsToFill = null;
[HideInInspector]
public bool reverseDirection = false;
[HideInInspector]
public float rubberScale = 1;
[HideInInspector]
public float threshold = 100;
[HideInInspector]
public bool lowGrid = false;
private int m_nFrame = 3;
private bool m_bClampedMove = false;
protected int itemTypeStart = 0;
protected int itemTypeEnd = 0;
protected abstract float GetSize(RectTransform item);
protected abstract float GetDimension(Vector2 vector);
protected abstract Vector2 GetVector(float value);
protected int directionSign = 0;
private float m_ContentSpacing = -1;
protected GridLayoutGroup m_GridLayout = null;
protected float contentSpacing
{
get
{
if (m_ContentSpacing >= 0)
{
return m_ContentSpacing;
}
m_ContentSpacing = 0;
if (content != null)
{
HorizontalOrVerticalLayoutGroup layout1 = content.GetComponent<HorizontalOrVerticalLayoutGroup>();
if (layout1 != null)
{
m_ContentSpacing = layout1.spacing;
}
m_GridLayout = content.GetComponent<GridLayoutGroup>();
if (m_GridLayout != null)
{
m_ContentSpacing = GetDimension(m_GridLayout.spacing);
}
}
return m_ContentSpacing;
}
}
protected int mContentCount = 0;
public int ContentCount
{
get { return mContentCount; }
set { mContentCount = value; }
}
private UpdateCellCallback mCellCallback = null;
int mMovePos = 0;
bool dirty = false;
bool force = false;
public UpdateCellCallback CellCallback
{
set { mCellCallback = value; }
}
private LuaUpdateCellCallback mLuaCellCallback = null;
private LuaTable luaSelf = null;
private LuaFunction mCellCallbackLua = null;
public LuaFunction CellCallbackLua
{
set { mCellCallbackLua = value; }
}
private LuaFunction mLuaCellCallbackLua = null;
private int m_ContentConstraintCount = 0;
public int contentConstraintCount
{
get
{
if (m_ContentConstraintCount > 0)
{
return m_ContentConstraintCount;
}
m_ContentConstraintCount = 1;
if (content != null)
{
GridLayoutGroup layout2 = content.GetComponent<GridLayoutGroup>();
if (layout2 != null)
{
if (layout2.constraint == GridLayoutGroup.Constraint.Flexible)
{
DebugHelper.Log("[LoopScrollRect] Flexible not supported yet");
}
m_ContentConstraintCount = layout2.constraintCount;
}
}
return m_ContentConstraintCount;
}
}
public int ItemTypeStart
{
get { return itemTypeStart; }
}
public int ItemTypeEnd
{
get { return itemTypeEnd; }
}
protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds) { return false; }
//==========LoopScrollRect==========
public enum MovementType
{
Unrestricted, // Unrestricted movement -- can scroll forever
Elastic, // Restricted but flexible -- can go past the edges, but springs back in place
Clamped, // Restricted movement where it's not possible to go past the edges
}
public enum ScrollbarVisibility
{
Permanent,
AutoHide,
AutoHideAndExpandViewport,
}
[Serializable]
public class ScrollRectEvent : UnityEvent<Vector2> { }
[SerializeField]
private Transform m_Cell;
public Transform Cell { get { return m_Cell; } set { m_Cell = value; } }
[SerializeField]
private RectTransform m_Content;
public RectTransform content { get { return m_Content; } set { m_Content = value; } }
[SerializeField]
private bool m_Horizontal = true;
public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }
[SerializeField]
private bool m_Vertical = true;
public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } }
[SerializeField]
private MovementType m_MovementType = MovementType.Elastic;
public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } }
[SerializeField]
private float m_Elasticity = 0.1f; // Only used for MovementType.Elastic
public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } }
[SerializeField]
private bool m_Inertia = true;
public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } }
[SerializeField]
private float m_DecelerationRate = 0.135f; // Only used when inertia is enabled
public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } }
[SerializeField]
private float m_ScrollSensitivity = 1.0f;
public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } }
[SerializeField]
private RectTransform m_Viewport;
public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } }
[SerializeField]
private Scrollbar m_HorizontalScrollbar;
public Scrollbar horizontalScrollbar
{
get
{
return m_HorizontalScrollbar;
}
set
{
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
m_HorizontalScrollbar = value;
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
private Scrollbar m_VerticalScrollbar;
public Scrollbar verticalScrollbar
{
get
{
return m_VerticalScrollbar;
}
set
{
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
m_VerticalScrollbar = value;
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
private ScrollbarVisibility m_HorizontalScrollbarVisibility;
public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } }
[SerializeField]
private ScrollbarVisibility m_VerticalScrollbarVisibility;
public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } }
[SerializeField]
private float m_HorizontalScrollbarSpacing;
public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } }
[SerializeField]
private float m_VerticalScrollbarSpacing;
public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } }
[SerializeField]
private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent();
public ScrollRectEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
// The offset from handle position to mouse down position
private Vector2 m_PointerStartLocalCursor = Vector2.zero;
private Vector2 m_ContentStartPosition = Vector2.zero;
private RectTransform m_ViewRect;
protected RectTransform viewRect
{
get
{
if (m_ViewRect == null)
m_ViewRect = m_Viewport;
if (m_ViewRect == null)
m_ViewRect = (RectTransform)transform;
return m_ViewRect;
}
}
private Bounds m_ContentBounds;
private Bounds m_ViewBounds;
private Vector2 m_Velocity;
public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } }
private bool m_Dragging;
public bool IsDragging { get { return m_Dragging; } }
private Vector2 m_PrevPosition = Vector2.zero;
private Bounds m_PrevContentBounds;
private Bounds m_PrevViewBounds;
[NonSerialized]
private bool m_HasRebuiltLayout = false;
private bool m_HSliderExpand;
private bool m_VSliderExpand;
private float m_HSliderHeight;
private float m_VSliderWidth;
[System.NonSerialized]
private RectTransform m_Rect;
private RectTransform rectTransform
{
get
{
if (m_Rect == null)
m_Rect = GetComponent<RectTransform>();
return m_Rect;
}
}
private RectTransform m_HorizontalScrollbarRect;
private RectTransform m_VerticalScrollbarRect;
private DrivenRectTransformTracker m_Tracker;
public System.Action mOnBeginDragAction = null;
public System.Action mOnDragingAction = null;
public System.Action mOnEndDragAction = null;
private LuaFunction mOnBeginDragLuaFun = null;
private LuaFunction mOnDragLuaFun = null;
private LuaFunction mOnEndDragLuaFun = null;
protected LoopScrollRect()
{
flexibleWidth = -1;
}
public void ForbidMove()
{
m_MovementType = MovementType.Clamped;
}
int preIdx = 0;
//==========LoopScrollRect==========
void UpdateScrollCell(Transform go,int idx)
{
if(mCellCallback != null)
{
mCellCallback(go.gameObject, idx);
}
if(luaSelf!=null && mLuaCellCallback!=null)
{
preIdx = idx;
mLuaCellCallback.DynamicInvoke(luaSelf, go.gameObject,idx);
}
}
public void ClearCells()
{
if (Application.isPlaying)
{
/*Vector2 sizeDelta = m_Content.sizeDelta;
sizeDelta.x = 0;
sizeDelta.y = 0;
m_Content.sizeDelta = sizeDelta;*/
itemTypeStart = 0;
itemTypeEnd = 0;
totalCount = 0;
objectsToFill = null;
for (int i = content.childCount - 1; i >= 0; i--)
{
GarbageGo(content.GetChild(i).gameObject);
}
//m_Content.anchoredPosition3D = Vector3.zero;
}
}
public void ClearAnchoredPostion()
{
if (null == m_Content)
return;
m_Content.anchoredPosition3D = Vector3.zero;
}
public void ResetClampOffset()
{
m_nFrame = 3;
m_bClampedMove = true;
}
public void Reset()
{
itemTypeStart = 0;
itemTypeEnd = 0;
totalCount = 0;
objectsToFill = null;
}
public void DestroyCells()
{
DestroyCells(false);
}
public void DestroyCells(bool immediate)
{
itemTypeStart = 0;
itemTypeEnd = 0;
totalCount = 0;
for (int i = content.childCount-1; i>=0;i--)
{
//这里改成DestroyImmediate确保childCount立即更新
if(immediate)
{
content.GetChild(i).gameObject.DestroyImmediate();
}
else
{
GameObject.Destroy(content.GetChild(i).gameObject);
}
//
}
while(mAvailableObj.Count > 0)
{
if(immediate)
{
var go = mAvailableObj.Pop();
if (go != null)
go.DestroyImmediate();
}
else
{
GameObject.Destroy(mAvailableObj.Pop());
}
}
}
public void ResetItemTypeStart()
{
itemTypeStart = 0;
}
public void RefreshCells()
{
if (Application.isPlaying && this.isActiveAndEnabled)
{
itemTypeEnd = itemTypeStart;
// recycle items if we can
for (int i = 0; i < content.childCount; i++)
{
if (itemTypeEnd < totalCount)
{
UpdateScrollCell(content.GetChild(i), itemTypeEnd);
itemTypeEnd++;
}
else
{
GarbageGo(content.GetChild(i).gameObject);
i--;
}
}
}
}
public void RefreshCellsData()
{
if (Application.isPlaying && this.isActiveAndEnabled)
{
for (int idx = itemTypeStart; idx < totalCount; idx++)
{
int childIdx = idx - itemTypeStart;
if (childIdx < content.childCount)
{
UpdateScrollCell(content.GetChild(childIdx), idx);
}
}
}
}
public void MoveTo(int index)
{
if (index < 0 || index >= totalCount)
return;
mMovePos = index;
dirty = true;
force = false;
}
public void ForceMoveTo(int index)
{
if (index < 0 || index >= totalCount)
return;
mMovePos = index;
dirty = true;
force = true;
}
public void SetItemStartIdx(int index)
{
itemTypeStart = index;
itemTypeEnd = index;
}
public void SetUpdateCellCallback(LuaTable self,LuaUpdateCellCallback cb)
{
luaSelf = self;
mLuaCellCallback = cb;
}
public void SetDragLuaCallback(LuaFunction cb)
{
mOnDragLuaFun = cb;
}
public void SetOnDragLuaCallback(LuaFunction cb)
{
mOnBeginDragLuaFun = cb;
}
public void SetOnEndDragLuaCallback(LuaFunction cb)
{
mOnEndDragLuaFun = cb;
}
protected float NewItemAtStart()
{
if (totalCount >= 0 && itemTypeStart - contentConstraintCount < 0)
{
return 0;
}
float size = 0;
for (int i = 0; i < contentConstraintCount; i++)
{
itemTypeStart--;
RectTransform newItem = InstantiateNextItem(itemTypeStart);
newItem.SetAsFirstSibling();
size = Mathf.Max(GetSize(newItem), size);
}
if (!reverseDirection)
{
Vector2 offset = GetVector(size);
content.anchoredPosition += offset;
m_PrevPosition += offset;
m_ContentStartPosition += offset;
}
return size;
}
protected float DeleteItemAtStart()
{
if ((totalCount >= 0 && itemTypeEnd >= totalCount - 1) || content.childCount == 0)
{
return 0;
}
float size = 0;
for (int i = 0; i < contentConstraintCount; i++)
{
RectTransform oldItem = content.GetChild(0) as RectTransform;
size = Mathf.Max(GetSize(oldItem), size);
GarbageGo(oldItem.gameObject);
itemTypeStart++;
if (content.childCount == 0)
{
break;
}
}
if (!reverseDirection)
{
Vector2 offset = GetVector(size);
content.anchoredPosition -= offset;
m_PrevPosition -= offset;
m_ContentStartPosition -= offset;
}
return size;
}
/// <summary>
/// 在尾部追加一个元素
/// </summary>
/// <returns></returns>
protected float NewItemAtEnd()
{
if (totalCount >= 0 && itemTypeEnd >= totalCount)
{
return 0;
}
float size = 0;
// issue 4: fill lines to end first
int count = contentConstraintCount - (content.childCount % contentConstraintCount);
for (int i = 0; i < count; i++)
{
RectTransform newItem = InstantiateNextItem(itemTypeEnd);
size = Mathf.Max(GetSize(newItem), size);
itemTypeEnd++;
if (totalCount >= 0 && itemTypeEnd >= totalCount)
{
break;
}
}
if (reverseDirection)
{
Vector2 offset = GetVector(size);
content.anchoredPosition -= offset;
m_PrevPosition -= offset;
m_ContentStartPosition -= offset;
}
return size;
}
/// <summary>
/// 删除列表后面的元素
/// </summary>
/// <returns></returns>
protected float DeleteItemAtEnd()
{
if ((totalCount >= 0 && itemTypeStart < contentConstraintCount) || content.childCount == 0 || itemTypeEnd == 0)
{
return 0;
}
float size = 0;
for (int i = 0; i < contentConstraintCount; i++)
{
RectTransform oldItem = content.GetChild(content.childCount - 1) as RectTransform;
size = Mathf.Max(GetSize(oldItem), size);
GarbageGo(oldItem.gameObject);
itemTypeEnd--;
if (itemTypeEnd % contentConstraintCount == 0 || content.childCount == 0)
{
break; //just delete the whole row
}
}
if (reverseDirection)
{
Vector2 offset = GetVector(size);
content.anchoredPosition += offset;
m_PrevPosition += offset;
m_ContentStartPosition += offset;
}
return size;
}
private RectTransform InstantiateNextItem(int itemIdx)
{
GameObject go = CreateGO();
if (go == null) return null;
RectTransform nextItem = go.GetComponent<RectTransform>();
nextItem.transform.SetParent(content, false);
nextItem.gameObject.SetActive(true);
UpdateScrollCell(nextItem, itemIdx);
return nextItem;
}
Stack<GameObject> mAvailableObj = new Stack<GameObject>();
GameObject CreateGO()
{
if (m_Cell == null) return null;
GameObject go = null;
if(mAvailableObj.Count > 0)
{
go = mAvailableObj.Pop();
}else
{
go = GameObject.Instantiate(m_Cell.gameObject);
go.transform.SetParent(content, false);
go.transform.localScale = m_Cell.transform.localScale;
}
return go;
}
void GarbageGo(GameObject go)
{
go.SetActive(false);
go.transform.SetParent(this.transform, false);
mAvailableObj.Push(go);
}
//==========LoopScrollRect==========
public virtual void Rebuild(CanvasUpdate executing)
{
if (executing == CanvasUpdate.Prelayout)
{
UpdateCachedData();
}
if (executing == CanvasUpdate.PostLayout)
{
UpdateBounds(false);
UpdateScrollbars(Vector2.zero);
UpdatePrevData();
m_HasRebuiltLayout = true;
}
}
public virtual void LayoutComplete()
{ }
public virtual void GraphicUpdateComplete()
{ }
void UpdateCachedData()
{
Transform transform = this.transform;
m_HorizontalScrollbarRect = m_HorizontalScrollbar == null ? null : m_HorizontalScrollbar.transform as RectTransform;
m_VerticalScrollbarRect = m_VerticalScrollbar == null ? null : m_VerticalScrollbar.transform as RectTransform;
// These are true if either the elements are children, or they don't exist at all.
bool viewIsChild = (viewRect.parent == transform);
bool hScrollbarIsChild = (!m_HorizontalScrollbarRect || m_HorizontalScrollbarRect.parent == transform);
bool vScrollbarIsChild = (!m_VerticalScrollbarRect || m_VerticalScrollbarRect.parent == transform);
bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild);
m_HSliderExpand = allAreChildren && m_HorizontalScrollbarRect && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
m_VSliderExpand = allAreChildren && m_VerticalScrollbarRect && verticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
m_HSliderHeight = (m_HorizontalScrollbarRect == null ? 0 : m_HorizontalScrollbarRect.rect.height);
m_VSliderWidth = (m_VerticalScrollbarRect == null ? 0 : m_VerticalScrollbarRect.rect.width);
}
protected override void OnEnable()
{
base.OnEnable();
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
protected override void OnDisable()
{
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
m_HasRebuiltLayout = false;
m_Tracker.Clear();
m_Velocity = Vector2.zero;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
public override bool IsActive()
{
return base.IsActive() && m_Content != null;
}
private void EnsureLayoutHasRebuilt()
{
if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
Canvas.ForceUpdateCanvases();
}
public virtual void StopMovement()
{
m_Velocity = Vector2.zero;
}
public void SetScrollToPosition(Vector2 pos)
{
DebugHelper.LogError(pos);
SetContentAnchoredPosition(pos);
}
public virtual void OnScroll(PointerEventData data)
{
if (!IsActive())
return;
EnsureLayoutHasRebuilt();
UpdateBounds();
Vector2 delta = data.scrollDelta;
// Down is positive for scroll events, while in UI system up is positive.
delta.y *= -1;
if (vertical && !horizontal)
{
if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
delta.y = delta.x;
delta.x = 0;
}
if (horizontal && !vertical)
{
if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x))
delta.x = delta.y;
delta.y = 0;
}
Vector2 position = m_Content.anchoredPosition;
position += delta * m_ScrollSensitivity;
if (m_MovementType == MovementType.Clamped)
position += CalculateOffset(position - m_Content.anchoredPosition);
SetContentAnchoredPosition(position);
UpdateBounds();
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
m_Velocity = Vector2.zero;
}
public virtual void OnBeginDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
if (!IsActive())
return;
UpdateBounds();
m_PointerStartLocalCursor = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor);
m_ContentStartPosition = m_Content.anchoredPosition;
m_Dragging = true;
if (mOnBeginDragAction != null)
mOnBeginDragAction();
if(mOnBeginDragLuaFun != null && luaSelf != null)
mOnBeginDragLuaFun.Call(luaSelf);
}
public virtual void OnEndDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
m_Dragging = false;
if (mOnEndDragAction != null)
mOnEndDragAction();
if (mOnEndDragLuaFun != null && luaSelf != null)
mOnEndDragLuaFun.Call(luaSelf);
}
public virtual void OnDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
if (!IsActive())
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor))
return;
UpdateBounds();
var pointerDelta = localCursor - m_PointerStartLocalCursor;
Vector2 position = m_ContentStartPosition + pointerDelta;
// Offset to get content into place in the view.
Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition);
position += offset;
if (m_MovementType == MovementType.Elastic)
{
//==========LoopScrollRect==========
if (offset.x != 0)
position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x) * rubberScale;
if (offset.y != 0)
position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y) * rubberScale;
//==========LoopScrollRect==========
}
SetContentAnchoredPosition(position);
if (mOnDragingAction != null)
mOnDragingAction();
if (mOnDragLuaFun != null && luaSelf != null)
mOnDragLuaFun.Call(luaSelf);
}
protected virtual void SetContentAnchoredPosition(Vector2 position)
{
if (!m_Horizontal)
position.x = m_Content.anchoredPosition.x;
if (!m_Vertical)
position.y = m_Content.anchoredPosition.y;
if (position != m_Content.anchoredPosition)
{
m_Content.anchoredPosition = position;
UpdateBounds();
}
}
protected virtual void LateUpdate()
{
if (!m_Content)
return;
EnsureLayoutHasRebuilt();
UpdateScrollbarVisibility();
UpdateBounds();
float deltaTime = Time.unscaledDeltaTime;
Vector2 offset = CalculateOffset(Vector2.zero);
if (!m_Dragging && (offset != Vector2.zero || m_Velocity != Vector2.zero))
{
Vector2 position = m_Content.anchoredPosition;
for (int axis = 0; axis < 2; axis++)
{
// Apply spring physics if movement is elastic and content has an offset from the view.
if (m_MovementType == MovementType.Elastic && offset[axis] != 0)
{
float speed = m_Velocity[axis];
position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity, Mathf.Infinity, deltaTime);
m_Velocity[axis] = speed;
}
// Else move content according to velocity with deceleration applied.
else if (m_Inertia)
{
m_Velocity[axis] *= Mathf.Pow(m_DecelerationRate, deltaTime);
if (Mathf.Abs(m_Velocity[axis]) < 1)
m_Velocity[axis] = 0;
position[axis] += m_Velocity[axis] * deltaTime;
}
// If we have neither elaticity or friction, there shouldn't be any velocity.
else
{
m_Velocity[axis] = 0;
}
}
if (m_Velocity != Vector2.zero)
{
if (m_MovementType == MovementType.Clamped)
{
offset = CalculateOffset(position - m_Content.anchoredPosition);
position += offset;
}
SetContentAnchoredPosition(position);
}
}
//Clamped 模式 三帧后强制对齐确保加载后在iphonex适配下启用
if(m_bClampedMove)
{
if (m_nFrame != 0)
m_nFrame -= 1;
if (m_MovementType == MovementType.Clamped && offset.y != 0.0f && m_nFrame == 0)
{
Vector2 v2Postion = m_Content.anchoredPosition + offset;
SetContentAnchoredPosition(v2Postion);
m_bClampedMove = false;
}
}
if (m_Dragging && m_Inertia)
{
Vector3 newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime;
m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10);
}
if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition)
{
UpdateScrollbars(offset);
m_OnValueChanged.Invoke(normalizedPosition);
UpdatePrevData();
}
if (dirty && (mMovePos > 0 || force) && content.childCount >= mContentCount)
{
if (mMovePos == totalCount - 1)
{
itemTypeStart = totalCount - content.childCount + 1;
}
else if((mMovePos + content.childCount) >= totalCount -1)
{
itemTypeStart = totalCount - mContentCount;
}
else
itemTypeStart = mMovePos;
RefreshCells();
dirty = false;
mMovePos = 0;
force = false;
}
}
private void UpdatePrevData()
{
if (m_Content == null)
m_PrevPosition = Vector2.zero;
else
m_PrevPosition = m_Content.anchoredPosition;
m_PrevViewBounds = m_ViewBounds;
m_PrevContentBounds = m_ContentBounds;
}
private void UpdateScrollbars(Vector2 offset)
{
if (m_HorizontalScrollbar)
{
//==========LoopScrollRect==========
if (m_ContentBounds.size.x > 0 && totalCount > 0)
{
m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x * (itemTypeEnd - itemTypeStart) / totalCount);
}
//==========LoopScrollRect==========
else
m_HorizontalScrollbar.size = 1;
m_HorizontalScrollbar.value = horizontalNormalizedPosition;
}
if (m_VerticalScrollbar)
{
//==========LoopScrollRect==========
if (m_ContentBounds.size.y > 0 && totalCount > 0)
{
m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y * (itemTypeEnd - itemTypeStart) / totalCount);
}
//==========LoopScrollRect==========
else
m_VerticalScrollbar.size = 1;
m_VerticalScrollbar.value = verticalNormalizedPosition;
}
}
public Vector2 normalizedPosition
{
get
{
return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition);
}
set
{
SetNormalizedPosition(value.x, 0);
SetNormalizedPosition(value.y, 1);
}
}
public float horizontalNormalizedPosition
{
get
{
UpdateBounds(false);
//==========LoopScrollRect==========
if(totalCount > 0 && itemTypeEnd > itemTypeStart)
{
//TODO: space
float elementSize = m_ContentBounds.size.x / (itemTypeEnd - itemTypeStart);
float totalSize = elementSize * totalCount;
float offset = m_ContentBounds.min.x - elementSize * itemTypeStart;
if (totalSize <= m_ViewBounds.size.x)
return (m_ViewBounds.min.x > offset) ? 1 : 0;
return (m_ViewBounds.min.x - offset) / (totalSize - m_ViewBounds.size.x);
}
else
return 0.5f;
//==========LoopScrollRect==========
}
set
{
SetNormalizedPosition(value, 0);
}
}
public float verticalNormalizedPosition
{
get
{
UpdateBounds(false);
//==========LoopScrollRect==========
if(totalCount > 0 && itemTypeEnd > itemTypeStart)
{
//TODO: space
float elementSize = m_ContentBounds.size.y / (itemTypeEnd - itemTypeStart);
float totalSize = elementSize * totalCount;
float offset = m_ContentBounds.max.y + elementSize * itemTypeStart;
if (totalSize <= m_ViewBounds.size.y)
return (offset > m_ViewBounds.max.y) ? 1 : 0;
return (offset - m_ViewBounds.max.y) / (totalSize - m_ViewBounds.size.y);
}
else
return 0.5f;
//==========LoopScrollRect==========
}
set
{
SetNormalizedPosition(value, 1);
}
}
private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); }
private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); }
private void SetNormalizedPosition(float value, int axis)
{
//==========LoopScrollRect==========
if (totalCount <= 0 || itemTypeEnd <= itemTypeStart)
return;
//==========LoopScrollRect==========
EnsureLayoutHasRebuilt();
UpdateBounds();
//==========LoopScrollRect==========
Vector3 localPosition = m_Content.localPosition;
float newLocalPosition = localPosition[axis];
if (axis == 0)
{
float elementSize = m_ContentBounds.size.x / (itemTypeEnd - itemTypeStart);
float totalSize = elementSize * totalCount;
float offset = m_ContentBounds.min.x - elementSize * itemTypeStart;
newLocalPosition += m_ViewBounds.min.x - value * (totalSize - m_ViewBounds.size[axis]) - offset;
}
else if(axis == 1)
{
float elementSize = m_ContentBounds.size.y / (itemTypeEnd - itemTypeStart);
float totalSize = elementSize * totalCount;
float offset = m_ContentBounds.max.y + elementSize * itemTypeStart;
newLocalPosition -= offset - value * (totalSize - m_ViewBounds.size.y) - m_ViewBounds.max.y;
}
//==========LoopScrollRect==========
if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f)
{
localPosition[axis] = newLocalPosition;
m_Content.localPosition = localPosition;
m_Velocity[axis] = 0;
UpdateBounds();
}
}
private static float RubberDelta(float overStretching, float viewSize)
{
return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
}
protected override void OnRectTransformDimensionsChange()
{
SetDirty();
}
private bool hScrollingNeeded
{
get
{
if (Application.isPlaying)
return m_ContentBounds.size.x > m_ViewBounds.size.x + 0.01f;
return true;
}
}
private bool vScrollingNeeded
{
get
{
if (Application.isPlaying)
return m_ContentBounds.size.y > m_ViewBounds.size.y + 0.01f;
return true;
}
}
public virtual void CalculateLayoutInputHorizontal() { }
public virtual void CalculateLayoutInputVertical() { }
public virtual float minWidth { get { return -1; } }
public virtual float preferredWidth { get { return -1; } }
public virtual float flexibleWidth { get; private set; }
public virtual float minHeight { get { return -1; } }
public virtual float preferredHeight { get { return -1; } }
public virtual float flexibleHeight { get { return -1; } }
public virtual int layoutPriority { get { return -1; } }
public virtual void SetLayoutHorizontal()
{
m_Tracker.Clear();
if (m_HSliderExpand || m_VSliderExpand)
{
m_Tracker.Add(this, viewRect,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.SizeDelta |
DrivenTransformProperties.AnchoredPosition);
// Make view full size to see if content fits.
viewRect.anchorMin = Vector2.zero;
viewRect.anchorMax = Vector2.one;
viewRect.sizeDelta = Vector2.zero;
viewRect.anchoredPosition = Vector2.zero;
// Recalculate content layout with this size to see if it fits when there are no scrollbars.
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
}
// If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
if (m_VSliderExpand && vScrollingNeeded)
{
viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y);
// Recalculate content layout with this size to see if it fits vertically
// when there is a vertical scrollbar (which may reflowed the content to make it taller).
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
}
// If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it.
if (m_HSliderExpand && hScrollingNeeded)
{
viewRect.sizeDelta = new Vector2(viewRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing));
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
}
// If the vertical slider didn't kick in the first time, and the horizontal one did,
// we need to check again if the vertical slider now needs to kick in.
// If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
if (m_VSliderExpand && vScrollingNeeded && viewRect.sizeDelta.x == 0 && viewRect.sizeDelta.y < 0)
{
viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y);
}
}
public virtual void SetLayoutVertical()
{
UpdateScrollbarLayout();
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
}
void UpdateScrollbarVisibility()
{
if (m_VerticalScrollbar && m_VerticalScrollbarVisibility != ScrollbarVisibility.Permanent && m_VerticalScrollbar.gameObject.activeSelf != vScrollingNeeded)
m_VerticalScrollbar.gameObject.SetActive(vScrollingNeeded);
if (m_HorizontalScrollbar && m_HorizontalScrollbarVisibility != ScrollbarVisibility.Permanent && m_HorizontalScrollbar.gameObject.activeSelf != hScrollingNeeded)
m_HorizontalScrollbar.gameObject.SetActive(hScrollingNeeded);
}
void UpdateScrollbarLayout()
{
if (m_VSliderExpand && m_HorizontalScrollbar)
{
m_Tracker.Add(this, m_HorizontalScrollbarRect,
DrivenTransformProperties.AnchorMinX |
DrivenTransformProperties.AnchorMaxX |
DrivenTransformProperties.SizeDeltaX |
DrivenTransformProperties.AnchoredPositionX);
m_HorizontalScrollbarRect.anchorMin = new Vector2(0, m_HorizontalScrollbarRect.anchorMin.y);
m_HorizontalScrollbarRect.anchorMax = new Vector2(1, m_HorizontalScrollbarRect.anchorMax.y);
m_HorizontalScrollbarRect.anchoredPosition = new Vector2(0, m_HorizontalScrollbarRect.anchoredPosition.y);
if (vScrollingNeeded)
m_HorizontalScrollbarRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), m_HorizontalScrollbarRect.sizeDelta.y);
else
m_HorizontalScrollbarRect.sizeDelta = new Vector2(0, m_HorizontalScrollbarRect.sizeDelta.y);
}
if (m_HSliderExpand && m_VerticalScrollbar)
{
m_Tracker.Add(this, m_VerticalScrollbarRect,
DrivenTransformProperties.AnchorMinY |
DrivenTransformProperties.AnchorMaxY |
DrivenTransformProperties.SizeDeltaY |
DrivenTransformProperties.AnchoredPositionY);
m_VerticalScrollbarRect.anchorMin = new Vector2(m_VerticalScrollbarRect.anchorMin.x, 0);
m_VerticalScrollbarRect.anchorMax = new Vector2(m_VerticalScrollbarRect.anchorMax.x, 1);
m_VerticalScrollbarRect.anchoredPosition = new Vector2(m_VerticalScrollbarRect.anchoredPosition.x, 0);
if (hScrollingNeeded)
m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing));
else
m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, 0);
}
}
private void UpdateBounds(bool updateItems = true)
{
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
if (m_Content == null)
return;
// ============LoopScrollRect============
// Don't do this in Rebuild
if (Application.isPlaying && updateItems && UpdateItems(m_ViewBounds, m_ContentBounds))
{
Canvas.ForceUpdateCanvases();
m_ContentBounds = GetBounds();
}
// ============LoopScrollRect============
// Make sure content bounds are at least as large as view by adding padding if not.
// One might think at first that if the content is smaller than the view, scrolling should be allowed.
// However, that's not how scroll views normally work.
// Scrolling is *only* possible when content is *larger* than view.
// We use the pivot of the content rect to decide in which directions the content bounds should be expanded.
// E.g. if pivot is at top, bounds are expanded downwards.
// This also works nicely when ContentSizeFitter is used on the content.
Vector3 contentSize = m_ContentBounds.size;
Vector3 contentPos = m_ContentBounds.center;
Vector3 excess = m_ViewBounds.size - contentSize;
if (excess.x > 0)
{
contentPos.x -= excess.x * (m_Content.pivot.x - 0.5f);
contentSize.x = m_ViewBounds.size.x;
}
if (excess.y > 0)
{
contentPos.y -= excess.y * (m_Content.pivot.y - 0.5f);
contentSize.y = m_ViewBounds.size.y;
}
m_ContentBounds.size = contentSize;
m_ContentBounds.center = contentPos;
}
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 = viewRect.worldToLocalMatrix;
m_Content.GetWorldCorners(m_Corners);
for (int j = 0; j < 4; j++)
{
Vector3 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;
}
private Vector2 CalculateOffset(Vector2 delta)
{
Vector2 offset = Vector2.zero;
if (m_MovementType == MovementType.Unrestricted)
return offset;
Vector2 min = m_ContentBounds.min;
Vector2 max = m_ContentBounds.max;
if (m_Horizontal)
{
min.x += delta.x;
max.x += delta.x;
if (min.x > m_ViewBounds.min.x)
offset.x = m_ViewBounds.min.x - min.x;
else if (max.x < m_ViewBounds.max.x)
offset.x = m_ViewBounds.max.x - max.x;
}
if (m_Vertical)
{
min.y += delta.y;
max.y += delta.y;
if (max.y < m_ViewBounds.max.y)
offset.y = m_ViewBounds.max.y - max.y;
else if (min.y > m_ViewBounds.min.y)
offset.y = m_ViewBounds.min.y - min.y;
}
return offset;
}
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
protected void SetDirtyCaching()
{
if (!IsActive())
return;
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirtyCaching();
}
#endif
}
}