1using System.Collections;
2using System.Collections.Generic;
10 [RequireComponent(typeof(ScrollRect))]
11 [AddComponentMenu(
"UI/Extensions/ContentSnapScrollHorizontal")]
29 [Tooltip(
"The velocity below which the scroll rect will start to snap")]
32 [Header(
"Paging Info")]
33 [Tooltip(
"Should the pagination & buttons jump or lerp to the items")]
35 [Tooltip(
"The time it will take for the pagination or buttons to move between items")]
40 [Tooltip(
"Event is triggered whenever the scroll rect starts to move, even when triggered programmatically")]
46 return m_StartMovementEvent;
50 m_StartMovementEvent = value;
55 [Tooltip(
"Event is triggered whenever the closest item to the center of the scrollrect changes")]
56 private CurrentItemChangeEvent m_CurrentItemChangeEvent =
new CurrentItemChangeEvent();
61 return m_CurrentItemChangeEvent;
65 m_CurrentItemChangeEvent = value;
70 [Tooltip(
"Event is triggered when the ContentSnapScroll decides which item it is going to snap to. Returns the index of the closest position.")]
71 private FoundItemToSnapToEvent m_FoundItemToSnapToEvent =
new FoundItemToSnapToEvent();
76 return m_FoundItemToSnapToEvent;
80 m_FoundItemToSnapToEvent = value;
85 [Tooltip(
"Event is triggered when we finally settle on an element. Returns the index of the item's position.")]
86 private SnappedToItemEvent m_SnappedToItemEvent =
new SnappedToItemEvent();
91 return m_SnappedToItemEvent;
95 m_SnappedToItemEvent = value;
99 private ScrollRect scrollRect =
null;
100 private RectTransform scrollRectTransform =
null;
101 private RectTransform contentTransform =
null;
102 private List<Vector3> contentPositions =
null;
103 private Vector3 lerpTarget = Vector3.zero;
104 private float totalScrollableWidth = 0;
105 private DrivenRectTransformTracker tracker ;
106 private float mLerpTime = 0;
107 private int _closestItem = 0;
108 private bool mSliding =
false;
109 private bool mLerping =
false;
110 private bool ContentIsHorizonalLayoutGroup
114 return contentTransform.GetComponent<HorizontalLayoutGroup>() !=
null;
159 return contentPositions.IndexOf(FindClosestFrom(contentTransform.localPosition));
170 return contentPositions.IndexOf(lerpTarget);
178 scrollRect = GetComponent<ScrollRect>();
179 scrollRectTransform = (RectTransform) scrollRect.transform;
180 contentTransform = scrollRect.content;
188 SetupDrivenTransforms();
190 scrollRect.horizontalNormalizedPosition = 0;
195 private void OnDisable()
200 private void SetupDrivenTransforms()
202 tracker =
new DrivenRectTransformTracker();
206 foreach (RectTransform child
in contentTransform)
208 tracker.Add(
this, child, DrivenTransformProperties.Anchors);
210 child.anchorMax =
new Vector2(0, 1);
211 child.anchorMin =
new Vector2(0, 1);
215 private void SetupSnapScroll()
217 if (ContentIsHorizonalLayoutGroup)
221 SetupWithHorizontalLayoutGroup();
225 SetupWithCalculatedSpacing();
229 private void SetupWithHorizontalLayoutGroup()
231 HorizontalLayoutGroup horizLayoutGroup = contentTransform.GetComponent<HorizontalLayoutGroup>();
232 float childTotalWidths = 0;
233 int activeChildren = 0;
234 for (
int i = 0; i < contentTransform.childCount; i++)
238 childTotalWidths += ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
242 float spacingTotal = (activeChildren - 1) * horizLayoutGroup.spacing;
243 float totalWidth = childTotalWidths + spacingTotal + horizLayoutGroup.padding.left + horizLayoutGroup.padding.right;
245 contentTransform.sizeDelta =
new Vector2(totalWidth, contentTransform.sizeDelta.y);
246 float scrollRectWidth = Mathf.Min(((RectTransform)contentTransform.GetChild(0)).sizeDelta.x, ((RectTransform)contentTransform.GetChild(contentTransform.childCount - 1)).sizeDelta.x);
249 scrollRectTransform.sizeDelta =
new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
251 contentPositions =
new List<Vector3>();
252 float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
253 totalScrollableWidth = totalWidth - widthOfScrollRect;
254 float checkedChildrenTotalWidths = horizLayoutGroup.padding.left;
255 int activeChildrenBeforeSelf = 0;
256 for (
int i = 0; i < contentTransform.childCount; i++)
260 float widthOfSelf = ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
261 float offset = checkedChildrenTotalWidths + (horizLayoutGroup.spacing * activeChildrenBeforeSelf) + ((widthOfSelf - widthOfScrollRect) / 2);
262 scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
263 contentPositions.Add(contentTransform.localPosition);
265 checkedChildrenTotalWidths += widthOfSelf;
266 activeChildrenBeforeSelf++;
271 private void SetupWithCalculatedSpacing()
274 List<RectTransform> childrenFromLeftToRight =
new List<RectTransform>();
275 for (
int i = 0; i < contentTransform.childCount; i++)
279 RectTransform childBeingSorted = ((RectTransform)contentTransform.GetChild(i));
280 int insertIndex = childrenFromLeftToRight.Count;
281 for (
int j = 0; j < childrenFromLeftToRight.Count; j++)
283 if (DstFromTopLeftOfTransformToTopLeftOfParent(childBeingSorted).x < DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[j]).x)
289 childrenFromLeftToRight.Insert(insertIndex, childBeingSorted);
292 RectTransform childFurthestToTheRight = childrenFromLeftToRight[childrenFromLeftToRight.Count - 1];
293 float totalWidth = DstFromTopLeftOfTransformToTopLeftOfParent(childFurthestToTheRight).x + childFurthestToTheRight.sizeDelta.x;
295 contentTransform.sizeDelta =
new Vector2(totalWidth, contentTransform.sizeDelta.y);
296 float scrollRectWidth = Mathf.Min(childrenFromLeftToRight[0].sizeDelta.x, childrenFromLeftToRight[childrenFromLeftToRight.Count - 1].sizeDelta.x);
299 scrollRectTransform.sizeDelta =
new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
301 contentPositions =
new List<Vector3>();
302 float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
303 totalScrollableWidth = totalWidth - widthOfScrollRect;
304 for (
int i = 0; i < childrenFromLeftToRight.Count; i++)
306 float offset = DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[i]).x + ((childrenFromLeftToRight[i].sizeDelta.x - widthOfScrollRect) / 2);
307 scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
308 contentPositions.Add(contentTransform.localPosition);
313 #region Public Movement Functions
338 private void GoToChild(
int index,
bool jump)
340 int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1);
342 if (ContentIsHorizonalLayoutGroup)
344 lerpTarget = contentPositions[clampedIndex];
347 contentTransform.localPosition = lerpTarget;
352 StartCoroutine(
"LerpToContent");
357 int availableChildIndex = 0;
358 Vector3 previousContentTransformPos = contentTransform.localPosition;
359 for (
int i = 0; i < contentTransform.childCount; i++)
363 if (availableChildIndex == clampedIndex)
365 RectTransform startChild = (RectTransform) contentTransform.GetChild(i);
366 float offset = DstFromTopLeftOfTransformToTopLeftOfParent(startChild).x + ((startChild.sizeDelta.x - scrollRectTransform.sizeDelta.x) / 2);
367 scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
368 lerpTarget = contentTransform.localPosition;
371 contentTransform.localPosition = previousContentTransformPos;
373 StartCoroutine(
"LerpToContent");
377 availableChildIndex++;
383 private void GoToContentPos(
int index,
bool jump)
385 int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1);
390 lerpTarget = contentPositions[clampedIndex];
393 contentTransform.localPosition = lerpTarget;
398 StartCoroutine(
"LerpToContent");
445 SetupDrivenTransforms();
455 SetupDrivenTransforms();
461 #region Behind the Scenes Movement stuff
473 StartCoroutine(
"SlideAndLerp");
476 private void Update()
486 private IEnumerator SlideAndLerp()
494 lerpTarget = FindClosestFrom(contentTransform.localPosition);
497 while (Vector3.Distance(contentTransform.localPosition, lerpTarget) > 1)
499 contentTransform.localPosition = Vector3.Lerp(scrollRect.content.localPosition, lerpTarget, 7.5f * Time.deltaTime);
503 scrollRect.velocity =
Vector2.zero;
504 contentTransform.localPosition = lerpTarget;
508 private IEnumerator LerpToContent()
512 Vector3 originalContentPos = contentTransform.localPosition;
513 float elapsedTime = 0;
514 while (elapsedTime < mLerpTime)
516 elapsedTime += Time.deltaTime;
517 contentTransform.localPosition =
Vector3.Lerp(originalContentPos, lerpTarget, (elapsedTime / mLerpTime));
525 private void StopMovement()
527 scrollRect.velocity =
Vector2.zero;
528 StopCoroutine(
"SlideAndLerp");
529 StopCoroutine(
"LerpToContent");
532 private void ChangePaginationInfo(
int targetScreen)
535 for (
int i = 0; i <
pagination.transform.childCount; i++)
537 pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i);
541 private Vector2 DstFromTopLeftOfTransformToTopLeftOfParent(RectTransform rt)
544 return new Vector2(rt.anchoredPosition.x - (rt.sizeDelta.x * rt.pivot.x), rt.anchoredPosition.y + (rt.sizeDelta.y * (1 - rt.pivot.y)));
547 private Vector3 FindClosestFrom(Vector3 start)
550 float distance = Mathf.Infinity;
552 foreach (Vector3 position
in contentPositions)
554 if (
Vector3.Distance(start, position) < distance)
556 distance =
Vector3.Distance(start, position);
563 [System.Serializable]
567 [Tooltip(
"Child Index means the Index corresponds to the content item at that index in the hierarchy.\n" +
568 "Position Index means the Index corresponds to the content item in that snap position.\n" +
569 "A higher Position Index in a Horizontal Scroll Snap means it would be further to the right.")]
571 [Tooltip(
"Zero based")]
573 [Tooltip(
"If this is true the snap scroll will jump to the index, otherwise it will lerp there.")]
575 [Tooltip(
"If jump is false this is the time it will take to lerp to the index")]
UnityEngine.UI.Button Button
bool Lerping
Returns if the SnapScroll is moving programmatically
SnappedToItemEvent ItemSnappedTo
CurrentItemChangeEvent CurrentItemChanged
bool Moving
Returns if the SnapScroll is moving
int ClosestItemIndex
Returns the closest item's index *Note this is zero based, and based on position not child order
void UpdateLayoutAndMoveTo(MoveInfo info)
Recalculates the size of the content & snap positions, and moves to a new item afterwards.
void GoTo(MoveInfo info)
Function for going to a specific screen. *Note the index is based on a zero-starting index.
void UpdateLayout()
Function for recalculating the size of the content & the snap positions, such as when you remove or a...
int LerpTargetIndex
Returns the lerpTarget's index *Note this is zero-based, and based on position not child order
bool Sliding
Returns if the SnapScroll is moving because of a touch
void OnBeginDrag(PointerEventData ped)
FoundItemToSnapToEvent ItemFoundToSnap
StartMovementEvent MovementStarted
void OnEndDrag(PointerEventData ped)
void PreviousItem()
Function for going to the previous item *Note the next item is the item to the left of the current it...
void NextItem()
Function for going to the next item *Note the next item is the item to the right of the current item,...
int snappingVelocityThreshold
Credit Erdener Gonenc - @PixelEnvision.
MoveInfo(IndexType _indexType, int _index, bool _jump, float _duration)
Creates a MoveInfo
MoveInfo(IndexType _indexType, int _index)
Creates a MoveInfo that jumps to the index