Merged in beksomega/unity-ui-extensions/feature/content_scroll_snap (pull request #28)
Created a Content Scroll Snap that snaps onto the children of the content no matter their sizing or spacingrelease
commit
bf7d7e0830
|
@ -466,12 +466,119 @@ namespace UnityEditor.UI
|
||||||
{
|
{
|
||||||
FixedScrollSnapBase(menuCommand, "Scroll Snap Vertical Multiple", ScrollSnap.ScrollDirection.Vertical, 3, 15, new Vector2(100, 100));
|
FixedScrollSnapBase(menuCommand, "Scroll Snap Vertical Multiple", ScrollSnap.ScrollDirection.Vertical, 3, 15, new Vector2(100, 100));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#region ContentScrollSnapHorizontal
|
||||||
|
[MenuItem("GameObject/UI/Extensions/Content Scroll Snap Horizontal", false)]
|
||||||
|
static public void AddContentScrollSnapHorizontal(MenuCommand menuCommand)
|
||||||
|
{
|
||||||
|
GameObject contentScrollSnapRoot = CreateUIElementRoot("Content Scroll Snap Horizontal", menuCommand, s_ThickGUIElementSize);
|
||||||
|
|
||||||
#region UIVertical Scroller
|
GameObject childContent = CreateUIObject("Content", contentScrollSnapRoot);
|
||||||
[MenuItem("GameObject/UI/Extensions/UI Vertical Scroller", false)]
|
|
||||||
|
GameObject childPage01 = CreateUIObject("Position 1", childContent);
|
||||||
|
|
||||||
|
GameObject childPage02 = CreateUIObject("Position 2", childContent);
|
||||||
|
|
||||||
|
GameObject childPage03 = CreateUIObject("Position 3", childContent);
|
||||||
|
|
||||||
|
GameObject childPage04 = CreateUIObject("Position 4", childContent);
|
||||||
|
|
||||||
|
GameObject childPage05 = CreateUIObject("Position 5", childContent);
|
||||||
|
|
||||||
|
//setup root
|
||||||
|
RectTransform contentScrollSnapRectTransform = ((RectTransform)contentScrollSnapRoot.transform);
|
||||||
|
contentScrollSnapRectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
contentScrollSnapRectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
||||||
|
contentScrollSnapRectTransform.anchoredPosition = Vector2.zero;
|
||||||
|
contentScrollSnapRectTransform.sizeDelta = new Vector2(100, 200);
|
||||||
|
|
||||||
|
Image image = contentScrollSnapRoot.AddComponent<Image>();
|
||||||
|
image.sprite = null;
|
||||||
|
image.color = new Color(1, 0, 0, .5f);
|
||||||
|
|
||||||
|
ScrollRect sr = contentScrollSnapRoot.AddComponent<ScrollRect>();
|
||||||
|
sr.vertical = false;
|
||||||
|
sr.horizontal = true;
|
||||||
|
|
||||||
|
//setup content container
|
||||||
|
RectTransform contentTransform = ((RectTransform)childContent.transform);
|
||||||
|
contentTransform.anchorMin = new Vector2(.5f, .5f);
|
||||||
|
contentTransform.anchorMax = new Vector2(.5f, .5f);
|
||||||
|
contentTransform.pivot = new Vector2(.5f, .5f);
|
||||||
|
contentTransform.sizeDelta = new Vector2(200, 300);
|
||||||
|
|
||||||
|
Image contentImage = childContent.AddComponent<Image>();
|
||||||
|
contentImage.sprite = null;
|
||||||
|
contentImage.color = new Color(0, 0, 1, .5f);
|
||||||
|
|
||||||
|
sr.content = contentTransform;
|
||||||
|
|
||||||
|
//setup child 1
|
||||||
|
RectTransform childPage01Transform = (RectTransform)childPage01.transform;
|
||||||
|
childPage01Transform.anchorMin = new Vector2(0, 1);
|
||||||
|
childPage01Transform.anchorMax = new Vector2(0, 1);
|
||||||
|
childPage01Transform.pivot = new Vector2(0, 1);
|
||||||
|
childPage01Transform.anchoredPosition = new Vector2(0, -125);
|
||||||
|
|
||||||
|
Image childPage01Image = childPage01.AddComponent<Image>();
|
||||||
|
childPage01Image.sprite = null;
|
||||||
|
childPage01Image.color = Color.white;
|
||||||
|
|
||||||
|
//setup child 2
|
||||||
|
RectTransform childPage02Transform = (RectTransform)childPage02.transform;
|
||||||
|
childPage02Transform.anchorMin = new Vector2(0, 1);
|
||||||
|
childPage02Transform.anchorMax = new Vector2(0, 1);
|
||||||
|
childPage02Transform.pivot = new Vector2(0, 1);
|
||||||
|
childPage02Transform.anchoredPosition = new Vector2(175, -150);
|
||||||
|
|
||||||
|
Image childPage02Image = childPage02.AddComponent<Image>();
|
||||||
|
childPage02Image.sprite = null;
|
||||||
|
childPage02Image.color = Color.white;
|
||||||
|
|
||||||
|
//setup child 3
|
||||||
|
RectTransform childPage03Transform = (RectTransform)childPage03.transform;
|
||||||
|
childPage03Transform.anchorMin = new Vector2(0, 1);
|
||||||
|
childPage03Transform.anchorMax = new Vector2(0, 1);
|
||||||
|
childPage03Transform.pivot = new Vector2(0, 1);
|
||||||
|
childPage03Transform.anchoredPosition = new Vector2(315, -125);
|
||||||
|
childPage03Transform.sizeDelta = new Vector2(50, 100);
|
||||||
|
|
||||||
|
Image childPage03Image = childPage03.AddComponent<Image>();
|
||||||
|
childPage03Image.sprite = null;
|
||||||
|
childPage03Image.color = Color.white;
|
||||||
|
|
||||||
|
//setup child 4
|
||||||
|
RectTransform childPage04Transform = (RectTransform)childPage04.transform;
|
||||||
|
childPage04Transform.anchorMin = new Vector2(0, 1);
|
||||||
|
childPage04Transform.anchorMax = new Vector2(0, 1);
|
||||||
|
childPage04Transform.pivot = new Vector2(0, 1);
|
||||||
|
childPage04Transform.anchoredPosition = new Vector2(490, -110);
|
||||||
|
|
||||||
|
Image childPage04Image = childPage04.AddComponent<Image>();
|
||||||
|
childPage04Image.sprite = null;
|
||||||
|
childPage04Image.color = Color.white;
|
||||||
|
|
||||||
|
//setup child 5
|
||||||
|
RectTransform childPage05Transform = (RectTransform)childPage05.transform;
|
||||||
|
childPage05Transform.anchorMin = new Vector2(0, 1);
|
||||||
|
childPage05Transform.anchorMax = new Vector2(0, 1);
|
||||||
|
childPage05Transform.pivot = new Vector2(0, 1);
|
||||||
|
childPage05Transform.anchoredPosition = new Vector2(630, -180);
|
||||||
|
|
||||||
|
Image childPage05Image = childPage05.AddComponent<Image>();
|
||||||
|
childPage05Image.sprite = null;
|
||||||
|
childPage05Image.color = Color.white;
|
||||||
|
|
||||||
|
//add scroll snap after we've added the content & items
|
||||||
|
contentScrollSnapRoot.AddComponent<ContentScrollSnapHorizontal>();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UIVertical Scroller
|
||||||
|
[MenuItem("GameObject/UI/Extensions/UI Vertical Scroller", false)]
|
||||||
static public void AddUIVerticallScroller(MenuCommand menuCommand)
|
static public void AddUIVerticallScroller(MenuCommand menuCommand)
|
||||||
{
|
{
|
||||||
GameObject uiVerticalScrollerRoot = CreateUIElementRoot("UI Vertical Scroller", menuCommand, s_ThickGUIElementSize);
|
GameObject uiVerticalScrollerRoot = CreateUIElementRoot("UI Vertical Scroller", menuCommand, s_ThickGUIElementSize);
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0749bfbb194c58948abd4f5d4c46b6f6
|
||||||
|
timeCreated: 1511210771
|
||||||
|
licenseType: Free
|
||||||
|
DefaultImporter:
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,612 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace UnityEngine.UI.Extensions
|
||||||
|
{
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
[RequireComponent(typeof(ScrollRect))]
|
||||||
|
[AddComponentMenu("UI/Extensions/ContentSnapScrollHorizontal")]
|
||||||
|
public class ContentScrollSnapHorizontal : MonoBehaviour, IBeginDragHandler, IEndDragHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class StartMovementEvent : UnityEvent { }
|
||||||
|
[Serializable]
|
||||||
|
public class CurrentItemChangeEvent : UnityEvent<int> { }
|
||||||
|
[Serializable]
|
||||||
|
public class FoundItemToSnapToEvent : UnityEvent<int> { }
|
||||||
|
[Serializable]
|
||||||
|
public class SnappedToItemEvent : UnityEvent<int> { }
|
||||||
|
|
||||||
|
public bool ignoreInactiveItems = true;
|
||||||
|
public MoveInfo startInfo = new MoveInfo(MoveInfo.IndexType.positionIndex, 0);
|
||||||
|
public GameObject prevButton;
|
||||||
|
public GameObject nextButton;
|
||||||
|
public GameObject pagination;
|
||||||
|
[Tooltip("The velocity below which the scroll rect will start to snap")]
|
||||||
|
public int snappingVelocityThreshold = 50;
|
||||||
|
|
||||||
|
[Header("Paging Info")]
|
||||||
|
[Tooltip("Should the pagination & buttons jump or lerp to the items")]
|
||||||
|
public bool jumpToItem = false;
|
||||||
|
[Tooltip("The time it will take for the pagination or buttons to move between items")]
|
||||||
|
public float lerpTime = .3f;
|
||||||
|
|
||||||
|
[Header("Events")]
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Event is triggered whenever the scroll rect starts to move, even when triggered programatically")]
|
||||||
|
private StartMovementEvent m_StartMovementEvent = new StartMovementEvent();
|
||||||
|
public StartMovementEvent startMovementEvent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_StartMovementEvent;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_StartMovementEvent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Event is triggered whenever the closest item to the center of the scrollrect changes")]
|
||||||
|
private CurrentItemChangeEvent m_CurrentItemChangeEvent = new CurrentItemChangeEvent();
|
||||||
|
public CurrentItemChangeEvent currentItemChangeEvent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_CurrentItemChangeEvent;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_CurrentItemChangeEvent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Event is triggered when the ContentSnapScroll decides which item it is going to snap to. Returns the index of the closest position.")]
|
||||||
|
private FoundItemToSnapToEvent m_FoundItemToSnapToEvent = new FoundItemToSnapToEvent();
|
||||||
|
public FoundItemToSnapToEvent foundItemToSnapToEvent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_FoundItemToSnapToEvent;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_FoundItemToSnapToEvent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Event is triggered when we finally settle on an element. Returns the index of the item's position.")]
|
||||||
|
private SnappedToItemEvent m_SnappedToItemEvent = new SnappedToItemEvent();
|
||||||
|
public SnappedToItemEvent snappedToItemEvent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_SnappedToItemEvent;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_SnappedToItemEvent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScrollRect scrollRect;
|
||||||
|
private RectTransform scrollRectTransform;
|
||||||
|
private RectTransform contentTransform;
|
||||||
|
private List<Vector3> contentPositions;
|
||||||
|
private Vector3 lerpTarget;
|
||||||
|
private float totalScrollableWidth;
|
||||||
|
private DrivenRectTransformTracker tracker;
|
||||||
|
private bool lerp;
|
||||||
|
private bool pointerDown;
|
||||||
|
private float mLerpTime;
|
||||||
|
private int _closestItem;
|
||||||
|
private bool lerpToContentRunning;
|
||||||
|
private bool mSliding;
|
||||||
|
private bool mLerping;
|
||||||
|
private bool contentIsHorizonalLayoutGroup
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return contentTransform.GetComponent<HorizontalLayoutGroup>() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Info
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the SnapScroll is moving
|
||||||
|
/// </summary>
|
||||||
|
public bool moving
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return sliding || lerping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the SnapScroll is moving because of a touch
|
||||||
|
/// </summary>
|
||||||
|
public bool sliding
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mSliding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the SnapScroll is moving programmatically
|
||||||
|
/// </summary>
|
||||||
|
public bool lerping
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mLerping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the closest item's index
|
||||||
|
/// *Note this is zero based, and based on position not child order
|
||||||
|
/// </summary>
|
||||||
|
public int closestItemIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return contentPositions.IndexOf(FindClosestFrom(contentTransform.localPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the lerpTarget's index
|
||||||
|
/// *Note this is zero-based, and based on position not child order
|
||||||
|
/// </summary>
|
||||||
|
public int lerpTargetIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return contentPositions.IndexOf(lerpTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Setup
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
scrollRect = GetComponent<ScrollRect>();
|
||||||
|
scrollRectTransform = (RectTransform) scrollRect.transform;
|
||||||
|
contentTransform = scrollRect.content;
|
||||||
|
|
||||||
|
if (nextButton)
|
||||||
|
nextButton.GetComponent<Button>().onClick.AddListener(() => { NextItem(); });
|
||||||
|
|
||||||
|
if (prevButton)
|
||||||
|
prevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousItem(); });
|
||||||
|
|
||||||
|
SetupDrivenTransforms();
|
||||||
|
SetupSnapScroll();
|
||||||
|
scrollRect.horizontalNormalizedPosition = 0;
|
||||||
|
_closestItem = 0;
|
||||||
|
GoTo(startInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
tracker.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupDrivenTransforms()
|
||||||
|
{
|
||||||
|
tracker = new DrivenRectTransformTracker();
|
||||||
|
tracker.Clear();
|
||||||
|
|
||||||
|
//So that we can calculate everything correctly
|
||||||
|
foreach (RectTransform child in contentTransform)
|
||||||
|
{
|
||||||
|
tracker.Add(this, child, DrivenTransformProperties.Anchors);
|
||||||
|
|
||||||
|
child.anchorMax = new Vector2(0, 1);
|
||||||
|
child.anchorMin = new Vector2(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupSnapScroll()
|
||||||
|
{
|
||||||
|
if (contentIsHorizonalLayoutGroup)
|
||||||
|
{
|
||||||
|
//because you can't get the anchored positions of UI elements
|
||||||
|
//when they are in a layout group (as far as I could tell)
|
||||||
|
SetupWithHorizontalLayoutGroup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetupWithCalculatedSpacing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupWithHorizontalLayoutGroup()
|
||||||
|
{
|
||||||
|
HorizontalLayoutGroup horizLayoutGroup = contentTransform.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
float childTotalWidths = 0;
|
||||||
|
int activeChildren = 0;
|
||||||
|
for (int i = 0; i < contentTransform.childCount; i++)
|
||||||
|
{
|
||||||
|
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
childTotalWidths += ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
|
||||||
|
activeChildren++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float spacingTotal = (activeChildren - 1) * horizLayoutGroup.spacing;
|
||||||
|
float totalWidth = childTotalWidths + spacingTotal + horizLayoutGroup.padding.left + horizLayoutGroup.padding.right;
|
||||||
|
|
||||||
|
contentTransform.sizeDelta = new Vector2(totalWidth, contentTransform.sizeDelta.y);
|
||||||
|
float scrollRectWidth = Mathf.Min(((RectTransform)contentTransform.GetChild(0)).sizeDelta.x, ((RectTransform)contentTransform.GetChild(contentTransform.childCount - 1)).sizeDelta.x);
|
||||||
|
|
||||||
|
/*---If the scroll view is set to stretch width this breaks stuff---*/
|
||||||
|
scrollRectTransform.sizeDelta = new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
|
||||||
|
|
||||||
|
contentPositions = new List<Vector3>();
|
||||||
|
float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
|
||||||
|
totalScrollableWidth = totalWidth - widthOfScrollRect;
|
||||||
|
float checkedChildrenTotalWidths = horizLayoutGroup.padding.left;
|
||||||
|
int activeChildrenBeforeSelf = 0;
|
||||||
|
for (int i = 0; i < contentTransform.childCount; i++)
|
||||||
|
{
|
||||||
|
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
float widthOfSelf = ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
|
||||||
|
float offset = checkedChildrenTotalWidths + (horizLayoutGroup.spacing * activeChildrenBeforeSelf) + ((widthOfSelf - widthOfScrollRect) / 2);
|
||||||
|
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||||
|
contentPositions.Add(contentTransform.localPosition);
|
||||||
|
|
||||||
|
checkedChildrenTotalWidths += widthOfSelf;
|
||||||
|
activeChildrenBeforeSelf++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupWithCalculatedSpacing()
|
||||||
|
{
|
||||||
|
//we need them in order from left to right for pagination & buttons & our scrollRectWidth
|
||||||
|
List<RectTransform> childrenFromLeftToRight = new List<RectTransform>();
|
||||||
|
for (int i = 0; i < contentTransform.childCount; i++)
|
||||||
|
{
|
||||||
|
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
RectTransform childBeingSorted = ((RectTransform)contentTransform.GetChild(i));
|
||||||
|
int insertIndex = childrenFromLeftToRight.Count;
|
||||||
|
for (int j = 0; j < childrenFromLeftToRight.Count; j++)
|
||||||
|
{
|
||||||
|
if (DstFromTopLeftOfTransformToTopLeftOfParent(childBeingSorted).x < DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[j]).x)
|
||||||
|
{
|
||||||
|
insertIndex = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childrenFromLeftToRight.Insert(insertIndex, childBeingSorted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RectTransform childFurthestToTheRight = childrenFromLeftToRight[childrenFromLeftToRight.Count - 1];
|
||||||
|
float totalWidth = DstFromTopLeftOfTransformToTopLeftOfParent(childFurthestToTheRight).x + childFurthestToTheRight.sizeDelta.x;
|
||||||
|
|
||||||
|
contentTransform.sizeDelta = new Vector2(totalWidth, contentTransform.sizeDelta.y);
|
||||||
|
float scrollRectWidth = Mathf.Min(childrenFromLeftToRight[0].sizeDelta.x, childrenFromLeftToRight[childrenFromLeftToRight.Count - 1].sizeDelta.x);
|
||||||
|
|
||||||
|
// Note: sizeDelta will not be calculated properly if the scroll view is set to stretch width.
|
||||||
|
scrollRectTransform.sizeDelta = new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
|
||||||
|
|
||||||
|
contentPositions = new List<Vector3>();
|
||||||
|
float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
|
||||||
|
totalScrollableWidth = totalWidth - widthOfScrollRect;
|
||||||
|
for (int i = 0; i < childrenFromLeftToRight.Count; i++)
|
||||||
|
{
|
||||||
|
float offset = DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[i]).x + ((childrenFromLeftToRight[i].sizeDelta.x - widthOfScrollRect) / 2);
|
||||||
|
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||||
|
contentPositions.Add(contentTransform.localPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Movement Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Function for going to a specific screen.
|
||||||
|
/// *Note the index is based on a zero-starting index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">All of the info about how you want it to move</param>
|
||||||
|
public void GoTo(MoveInfo info)
|
||||||
|
{
|
||||||
|
if (!moving && info.index != closestItemIndex)
|
||||||
|
{
|
||||||
|
startMovementEvent.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.indexType == MoveInfo.IndexType.childIndex)
|
||||||
|
{
|
||||||
|
mLerpTime = info.duration;
|
||||||
|
GoToChild(info.index, info.jump);
|
||||||
|
}
|
||||||
|
else if (info.indexType == MoveInfo.IndexType.positionIndex)
|
||||||
|
{
|
||||||
|
mLerpTime = info.duration;
|
||||||
|
GoToContentPos(info.index, info.jump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoToChild(int index, bool jump)
|
||||||
|
{
|
||||||
|
int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1); //contentPositions amount == the amount of available children
|
||||||
|
|
||||||
|
if (contentIsHorizonalLayoutGroup) //the contentPositions are in child order
|
||||||
|
{
|
||||||
|
lerpTarget = contentPositions[clampedIndex];
|
||||||
|
if (jump)
|
||||||
|
{
|
||||||
|
contentTransform.localPosition = lerpTarget;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopMovement();
|
||||||
|
StartCoroutine("LerpToContent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else //the contentPositions are in order from left -> right;
|
||||||
|
{
|
||||||
|
int availableChildIndex = 0; //an available child is one we can snap to
|
||||||
|
Vector3 previousContentTransformPos = contentTransform.localPosition;
|
||||||
|
for (int i = 0; i < contentTransform.childCount; i++)
|
||||||
|
{
|
||||||
|
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
if (availableChildIndex == clampedIndex)
|
||||||
|
{
|
||||||
|
RectTransform startChild = (RectTransform) contentTransform.GetChild(i);
|
||||||
|
float offset = DstFromTopLeftOfTransformToTopLeftOfParent(startChild).x + ((startChild.sizeDelta.x - scrollRectTransform.sizeDelta.x) / 2);
|
||||||
|
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||||
|
lerpTarget = contentTransform.localPosition;
|
||||||
|
if (!jump)
|
||||||
|
{
|
||||||
|
contentTransform.localPosition = previousContentTransformPos;
|
||||||
|
StopMovement();
|
||||||
|
StartCoroutine("LerpToContent");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
availableChildIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoToContentPos(int index, bool jump)
|
||||||
|
{
|
||||||
|
int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1); //contentPositions amount == the amount of available children
|
||||||
|
|
||||||
|
//the content positions are all in order from left -> right
|
||||||
|
//which is what we want so there's no need to check
|
||||||
|
|
||||||
|
lerpTarget = contentPositions[clampedIndex];
|
||||||
|
if (jump)
|
||||||
|
{
|
||||||
|
contentTransform.localPosition = lerpTarget;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopMovement();
|
||||||
|
StartCoroutine("LerpToContent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function for going to the next item
|
||||||
|
/// *Note the next item is the item to the right of the current item, this is not based on child order
|
||||||
|
/// </summary>
|
||||||
|
public void NextItem()
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
if (sliding)
|
||||||
|
{
|
||||||
|
index = closestItemIndex + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = lerpTargetIndex + 1;
|
||||||
|
}
|
||||||
|
MoveInfo info = new MoveInfo(MoveInfo.IndexType.positionIndex, index, jumpToItem, lerpTime);
|
||||||
|
GoTo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function for going to the previous item
|
||||||
|
/// *Note the next item is the item to the left of the current item, this is not based on child order
|
||||||
|
/// </summary>
|
||||||
|
public void PreviousItem()
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
if (sliding)
|
||||||
|
{
|
||||||
|
index = closestItemIndex - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = lerpTargetIndex - 1;
|
||||||
|
}
|
||||||
|
MoveInfo info = new MoveInfo(MoveInfo.IndexType.positionIndex, index, jumpToItem, lerpTime);
|
||||||
|
GoTo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function for recalculating the size of the content & the snap positions, such as when you remove or add a child
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateLayout()
|
||||||
|
{
|
||||||
|
SetupDrivenTransforms();
|
||||||
|
SetupSnapScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculates the size of the content & snap positions, and moves to a new item afterwards.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">All of the info about how you want it to move</param>
|
||||||
|
public void UpdateLayoutAndMoveTo(MoveInfo info)
|
||||||
|
{
|
||||||
|
SetupDrivenTransforms();
|
||||||
|
SetupSnapScroll();
|
||||||
|
GoTo(info);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Behind the Scenes Movement stuff
|
||||||
|
public void OnBeginDrag(PointerEventData ped)
|
||||||
|
{
|
||||||
|
StopMovement();
|
||||||
|
if (!moving)
|
||||||
|
{
|
||||||
|
startMovementEvent.Invoke();
|
||||||
|
}
|
||||||
|
pointerDown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndDrag(PointerEventData ped)
|
||||||
|
{
|
||||||
|
pointerDown = false;
|
||||||
|
StartCoroutine("SlideAndLerp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_closestItem != closestItemIndex)
|
||||||
|
{
|
||||||
|
currentItemChangeEvent.Invoke(closestItemIndex);
|
||||||
|
ChangePaginationInfo(closestItemIndex);
|
||||||
|
_closestItem = closestItemIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator SlideAndLerp()
|
||||||
|
{
|
||||||
|
mSliding = true;
|
||||||
|
while (Mathf.Abs(scrollRect.velocity.x) > snappingVelocityThreshold)
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
lerpTarget = FindClosestFrom(contentTransform.localPosition);
|
||||||
|
foundItemToSnapToEvent.Invoke(lerpTargetIndex);
|
||||||
|
|
||||||
|
while (Vector3.Distance(contentTransform.localPosition, lerpTarget) > 1)
|
||||||
|
{
|
||||||
|
contentTransform.localPosition = Vector3.Lerp(scrollRect.content.localPosition, lerpTarget, 7.5f * Time.deltaTime);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
mSliding = false;
|
||||||
|
scrollRect.velocity = Vector2.zero;
|
||||||
|
contentTransform.localPosition = lerpTarget;
|
||||||
|
snappedToItemEvent.Invoke(lerpTargetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator LerpToContent()
|
||||||
|
{
|
||||||
|
foundItemToSnapToEvent.Invoke(lerpTargetIndex);
|
||||||
|
mLerping = true;
|
||||||
|
Vector3 originalContentPos = contentTransform.localPosition;
|
||||||
|
float elapsedTime = 0;
|
||||||
|
while (elapsedTime < mLerpTime)
|
||||||
|
{
|
||||||
|
elapsedTime += Time.deltaTime;
|
||||||
|
contentTransform.localPosition = Vector3.Lerp(originalContentPos, lerpTarget, (elapsedTime / mLerpTime));
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
snappedToItemEvent.Invoke(lerpTargetIndex);
|
||||||
|
mLerping = false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void StopMovement()
|
||||||
|
{
|
||||||
|
scrollRect.velocity = Vector2.zero;
|
||||||
|
StopCoroutine("SlideAndLerp");
|
||||||
|
StopCoroutine("LerpToContent");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangePaginationInfo(int targetScreen)
|
||||||
|
{
|
||||||
|
if (pagination)
|
||||||
|
for (int i = 0; i < pagination.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 DstFromTopLeftOfTransformToTopLeftOfParent(RectTransform rt)
|
||||||
|
{
|
||||||
|
//gets rid of any pivot weirdness
|
||||||
|
return new Vector2(rt.anchoredPosition.x - (rt.sizeDelta.x * rt.pivot.x), rt.anchoredPosition.y + (rt.sizeDelta.y * (1 - rt.pivot.y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 FindClosestFrom(Vector3 start)
|
||||||
|
{
|
||||||
|
Vector3 closest = Vector3.zero;
|
||||||
|
float distance = Mathf.Infinity;
|
||||||
|
|
||||||
|
foreach (Vector3 position in contentPositions)
|
||||||
|
{
|
||||||
|
if (Vector3.Distance(start, position) < distance)
|
||||||
|
{
|
||||||
|
distance = Vector3.Distance(start, position);
|
||||||
|
closest = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public struct MoveInfo
|
||||||
|
{
|
||||||
|
public enum IndexType { childIndex, positionIndex }
|
||||||
|
[Tooltip("Child Index means the Index coresponds to the content item at that index in the hierarchy.\n" +
|
||||||
|
"Position Index means the Index corresponds to the content item in that snap position.\n" +
|
||||||
|
"A higher Position Index in a Horizonal Scroll Snap means it would be further to the right.")]
|
||||||
|
public IndexType indexType;
|
||||||
|
[Tooltip("Zero based")]
|
||||||
|
public int index;
|
||||||
|
[Tooltip("If this is true the snap scroll will jump to the index, otherwise it will lerp there.")]
|
||||||
|
public bool jump;
|
||||||
|
[Tooltip("If jump is false this is the time it will take to lerp to the index")]
|
||||||
|
public float duration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a MoveInfo that jumps to the index
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_indexType">Whether you want to get the child at the index or the snap position at the index</param>
|
||||||
|
/// <param name="_index">Where you want it to jump</param>
|
||||||
|
public MoveInfo(IndexType _indexType, int _index)
|
||||||
|
{
|
||||||
|
indexType = _indexType;
|
||||||
|
index = _index;
|
||||||
|
jump = true;
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a MoveInfo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_indexType">Whether you want to get the child at the index or the snap position at the index</param>
|
||||||
|
/// <param name="_index">Where you want it to jump</param>
|
||||||
|
/// <param name="_jump">Whether you want it to jump or lerp to the index</param>
|
||||||
|
/// <param name="_duration">How long it takes to lerp to the index</param>
|
||||||
|
public MoveInfo(IndexType _indexType, int _index, bool _jump, float _duration)
|
||||||
|
{
|
||||||
|
indexType = _indexType;
|
||||||
|
index = _index;
|
||||||
|
jump = _jump;
|
||||||
|
duration = _duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b5e39a5eb1058947a287d21183b661c
|
||||||
|
timeCreated: 1511208372
|
||||||
|
licenseType: Free
|
||||||
|
MonoImporter:
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
Reference in New Issue