目前UGUI的ScrollRect不支持循环滑动,这里提供一种水平循环滑动的方法
简单描述一下思路:
(1)检测第一个物件的左边界和右边界,和Viewport的左边界比较。
(2)检测当前是向左滑动还是向右滑动
(3)修改Content的中心和边界,使Content的矩形随着具体内容的变化而变化
(4)左循环时把左边第一个元素放置到最右边
(5)右循环时把右边第一个元素放置到最左边。
直接放代码,使用方式和ScrollRect基本一致,注意设置枚举为HorizonLoop即可。
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
/// <summary>
/// Modified By HawkWang at 2017-04-07
/// All right not reserverd.
/// Copy and distribute and modify and delete at free will.
/// Too laze to support vertical loop scroll(and infact it is not as useful as horizon loop scroll).
/// </summary>
[AddComponentMenu("UI/Scroll Rect", 37)]
[SelectionBase]
[ExecuteInEditMode]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class ScrollRectExt : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
{
/// <summary>
/// Question One:研究哪里真正改变了Content的位置!
/// </summary>
#region ################ AddOn Content ##########################################
public enum EScrollType
{
HorizonLoop,
Horizon,
Vertical,
}
[SerializeField]
private EScrollType m_scrollType = EScrollType.HorizonLoop;
public enum EHorizonScrollDirection
{
Left,
Still,
Right,
}
EHorizonScrollDirection m_horizonDirection = EHorizonScrollDirection.Still;
public enum EMoveSiblingType
{
MoveFirstToLast,
MoveLastToFirst,
}
EMoveSiblingType m_moveSiblingType = EMoveSiblingType.MoveFirstToLast;
RectTransform FirstChild
{
get
{
return m_Content.GetChild(0) as RectTransform;
}
}
RectTransform LastChild
{
get
{
return m_Content.GetChild(m_Content.childCount - 1) as RectTransform;
}
}
float m_halfSize;
Vector2 m_viewPortWorldBound;//x 表示左边界,下边界,y表示右边界,上边界。
void InitExtContent()
{
if( horizontal && vertical == false )
{
HorizontalLayoutGroup hlg = m_Content.gameObject.GetComponent<HorizontalLayoutGroup>();
if( hlg != null )
{
m_halfSize = ( FirstChild.sizeDelta.x + hlg.spacing ) * 0.5f;
m_viewPortWorldBound = RectTransformUtility.WorldToScreenPoint(Camera.main, viewport.position);
m_viewPortWorldBound.y = m_viewPortWorldBound.x + viewport.sizeDelta.x;
}
}
if(vertical && horizontal == false)
{
VerticalLayoutGroup vlg = m_Content.gameObject.GetComponent<VerticalLayoutGroup>();
if( vlg != null )
{
//默认左上角对齐,所以下边界,采用的是x,上边界才是y
m_halfSize = (FirstChild.sizeDelta.y + vlg.spacing) * 0.5f;
m_viewPortWorldBound = RectTransformUtility.WorldToScreenPoint(Camera.main, viewport.position);
m_viewPortWorldBound.x = m_viewPortWorldBound.y - viewport.sizeDelta.y;
}
}
}
public bool horizontalLoop
{
get
{
return m_scrollType == EScrollType.HorizonLoop;
}
}
public bool horizontal
{
get
{
return m_scrollType == EScrollType.HorizonLoop || m_scrollType == EScrollType.Horizon;
}
}
public bool vertical
{
get
{
return m_scrollType == EScrollType.Vertical;
}
}
#endregion ########################################################################
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 RectTransform m_Content;
public RectTransform content { get { return m_Content; } set { m_Content = 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;
// 这个位置,记录的是,按下一瞬间,Content相对于锚点的位置,比如水平模式下
// 记录的是Content左上角,相对于ViewPort的左上角的位置。
// 作用:
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;
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;
protected ScrollRectExt()
{
flexibleWidth = -1;
}
public virtual void Rebuild(CanvasUpdate executing)
{
if (executing == CanvasUpdate.Prelayout)
{
UpdateCachedData();
}
if (executing == CanvasUpdate.PostLayout)
{
UpdateBounds();
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);
InitExtContent();
}
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 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,"Scroll");
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;
Debug.Log("Begin Drag Scrollrect ! content start position " + m_ContentStartPosition);
m_Dragging = true;
}
//结束拖拽
public virtual void OnEndDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
Debug.Log("End Drag ScrollRect !");
m_Dragging = false;
}
int frame_lock = -1;
public virtual void OnDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
if (!IsActive())
return;
Vector2 localCursor;
//莫名其妙,基本都是返回True的呀。应该是指,点击ScrollRect,并且点击在viewRect之内的双重条件。
//官网:当平面被Hit返回true,而不管点是不是在rect之内。这他妈有啥用....
bool result = RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor);
if (!result) return;
frame_lock--;
//试试在这里检测循环越界!
UpdateBounds();
Vector2 first_child_pos = RectTransformUtility.WorldToScreenPoint(Camera.main, FirstChild.position);
Vector2 left_bound_position = first_child_pos + new Vector2(m_halfSize, 0);
Vector2 right_bound_position = first_child_pos - new Vector2(m_halfSize, 0);
var pointerDelta = localCursor - m_PointerStartLocalCursor;
Vector2 position = m_ContentStartPosition + pointerDelta;
if (frame_lock < 0 && horizontalLoop)
{
if (m_horizonDirection == EHorizonScrollDirection.Left && left_bound_position.x < m_viewPortWorldBound.x)
{
m_ContentStartPosition.x += m_halfSize * 2;
m_ContentBounds.center = new Vector3(m_ContentBounds.center.x + m_halfSize * 2, m_ContentBounds.center.y, m_ContentBounds.center.z);
m_moveSiblingType = EMoveSiblingType.MoveFirstToLast;
UpdateBounds();
frame_lock = 2;
Debug.Log("Move One Item To Tail ");
}
else if (m_horizonDirection == EHorizonScrollDirection.Right && right_bound_position.x > m_viewPortWorldBound.x)
{
m_ContentStartPosition.x -= m_halfSize * 2;
m_ContentBounds.center = new Vector3(m_ContentBounds.center.x - m_halfSize * 2, m_ContentBounds.center.y, m_ContentBounds.center.z);
m_moveSiblingType = EMoveSiblingType.MoveLastToFirst;
UpdateBounds();
frame_lock = 2;
Debug.Log("Move One Item To Front ");
}
}
if (frame_lock == 1)
{
if (m_moveSiblingType == EMoveSiblingType.MoveFirstToLast)
{
FirstChild.SetAsLastSibling();
}
else if( m_moveSiblingType == EMoveSiblingType.MoveLastToFirst )
{
LastChild.SetAsFirstSibling();
}
}
// Offset to get content into place in the view.
Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition);
position += offset;
if (m_MovementType == MovementType.Elastic)
{
if (offset.x != 0)
position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x);
if (offset.y != 0)
position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y);
}
SetContentAnchoredPosition(position,"OnDrag");
}
protected virtual void SetContentAnchoredPosition(Vector2 position, string source = "")
{
if (!horizontal)
position.x = m_Content.anchoredPosition.x;
if (!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,"Late Update!");
}
}
if (m_Dragging && m_Inertia)
{
Vector3 newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime;
m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10);
}
//只要一拖动,立刻就会调用OnValueChange,这个Bound到底是什么东西?
if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition)
{
UpdateScrollbars(offset);
m_OnValueChanged.Invoke(normalizedPosition);
UpdatePrevData();
}
}
/// <summary>
/// 用于比较上一次的值,来确定是否需要出发OnValueChange等。
/// </summary>
private void UpdatePrevData()
{
if (m_Content == null)
m_PrevPosition = Vector2.zero;
else
{
if( m_Content.anchoredPosition.x - m_PrevPosition.x > 0.00001f )
{
m_horizonDirection = EHorizonScrollDirection.Right;
}
else if( m_Content.anchoredPosition.x - m_PrevPosition.x < -0.00001f )
{
m_horizonDirection = EHorizonScrollDirection.Left;
}
else
{
m_horizonDirection = EHorizonScrollDirection.Still;
}
Debug.Log(" direction is " + m_horizonDirection);
m_PrevPosition = m_Content.anchoredPosition;
}
m_PrevViewBounds = m_ViewBounds;
m_PrevContentBounds = m_ContentBounds;
}
private void UpdateScrollbars(Vector2 offset)
{
if (m_HorizontalScrollbar)
{
if (m_ContentBounds.size.x > 0)
m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x);
else
m_HorizontalScrollbar.size = 1;
m_HorizontalScrollbar.value = horizontalNormalizedPosition;
}
if (m_VerticalScrollbar)
{
if (m_ContentBounds.size.y > 0)
m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y);
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();
if (m_ContentBounds.size.x <= m_ViewBounds.size.x)
return (m_ViewBounds.min.x > m_ContentBounds.min.x) ? 1 : 0;
return (m_ViewBounds.min.x - m_ContentBounds.min.x) / (m_ContentBounds.size.x - m_ViewBounds.size.x);
}
set
{
SetNormalizedPosition(value, 0);
}
}
public float verticalNormalizedPosition
{
get
{
UpdateBounds();
if (m_ContentBounds.size.y <= m_ViewBounds.size.y)
return (m_ViewBounds.min.y > m_ContentBounds.min.y) ? 1 : 0;
;
return (m_ViewBounds.min.y - m_ContentBounds.min.y) / (m_ContentBounds.size.y - m_ViewBounds.size.y);
}
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)
{
EnsureLayoutHasRebuilt();
UpdateBounds();
// How much the content is larger than the view.
float hiddenLength = m_ContentBounds.size[axis] - m_ViewBounds.size[axis];
// Where the position of the lower left corner of the content bounds should be, in the space of the view.
float contentBoundsMinPosition = m_ViewBounds.min[axis] - value * hiddenLength;
// The new content localPosition, in the space of the view.
float newLocalPosition = m_Content.localPosition[axis] + contentBoundsMinPosition - m_ContentBounds.min[axis];
Vector3 localPosition = m_Content.localPosition;
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);
}
}
/// <summary>
/// 目前推测,最后HorizonLayout是根据ContentBound来计算每个位置的偏移的
/// </summary>
private void UpdateBounds()
{
m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
m_ContentBounds = GetBounds();
if (m_Content == null)
return;
// 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 (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 (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
}
}
版权声明:本文为WangHaoDiablo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。