/// Credit BinaryX /// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602 /// Updated by simonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect. /// Update by xesenix - rewrote almost the entire code /// - configuration for direction move instead of 2 concurrent class (easier to change direction in editor) /// - supports list layout with horizontal or vertical layout need to match direction with type of layout used /// - dynamic checks if scrolled list size changes and recalculates anchor positions /// and item size based on itemsVisibleAtOnce and size of root container /// if you don't wish to use this auto resize turn of autoLayoutItems /// - fixed current page made it independent from pivot /// - replaced pagination with delegate function using System; using UnityEngine.EventSystems; namespace UnityEngine.UI.Extensions { [ExecuteInEditMode] [RequireComponent(typeof(ScrollRect))] [AddComponentMenu("UI/Extensions/Scroll Snap")] public class ScrollSnap : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollSnap { // needed because of reversed behaviour of axis Y compared to X // (positions of children lower in children list in horizontal directions grows when in vertical it gets smaller) public enum ScrollDirection { Horizontal, Vertical } private ScrollRect _scroll_rect; private RectTransform _scrollRectTransform; private Transform _listContainerTransform; //private RectTransform _rectTransform; private int _pages; private int _startingPage = 0; // anchor points to lerp to see child on certain indexes private Vector3[] _pageAnchorPositions; private Vector3 _lerpTarget; private bool _lerp; // item list related private float _listContainerMinPosition; private float _listContainerMaxPosition; private float _listContainerSize; private RectTransform _listContainerRectTransform; private Vector2 _listContainerCachedSize; private float _itemSize; private int _itemsCount = 0; // drag related private bool _startDrag = true; private Vector3 _positionOnDragStart = new Vector3(); private int _pageOnDragStart; private bool _fastSwipeTimer = false; private int _fastSwipeCounter = 0; private int _fastSwipeTarget = 10; [Tooltip("Button to go to the next page. (optional)")] public Button NextButton; [Tooltip("Button to go to the previous page. (optional)")] public Button PrevButton; [Tooltip("Number of items visible in one page of scroll frame.")] [RangeAttribute(1, 100)] public int ItemsVisibleAtOnce = 1; [Tooltip("Sets minimum width of list items to 1/itemsVisibleAtOnce.")] public bool AutoLayoutItems = true; [Tooltip("If you wish to update scrollbar numberOfSteps to number of active children on list.")] public bool LinkScrolbarSteps = false; [Tooltip("If you wish to update scrollrect sensitivity to size of list element.")] public bool LinkScrolrectScrollSensitivity = false; public Boolean UseFastSwipe = true; public int FastSwipeThreshold = 100; public delegate void PageSnapChange(int page); public event PageSnapChange onPageChange; public ScrollDirection direction = ScrollDirection.Horizontal; // Use this for initialization void Start() { _lerp = false; _scroll_rect = gameObject.GetComponent<ScrollRect>(); _scrollRectTransform = gameObject.GetComponent<RectTransform>(); _listContainerTransform = _scroll_rect.content; _listContainerRectTransform = _listContainerTransform.GetComponent<RectTransform>(); //_rectTransform = _listContainerTransform.gameObject.GetComponent<RectTransform>(); UpdateListItemsSize(); UpdateListItemPositions(); PageChanged(CurrentPage()); if (NextButton) { NextButton.GetComponent<Button>().onClick.AddListener(() => { NextScreen(); }); } if (PrevButton) { PrevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousScreen(); }); } if (_scroll_rect.horizontalScrollbar != null && _scroll_rect.horizontal) { var hscroll = _scroll_rect.horizontalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>(); hscroll.ss = this; } if (_scroll_rect.verticalScrollbar != null && _scroll_rect.vertical) { var vscroll = _scroll_rect.verticalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>(); vscroll.ss = this; } } public void UpdateListItemsSize() { float size = 0; float currentSize = 0; if (direction == ScrollSnap.ScrollDirection.Horizontal) { size = _scrollRectTransform.rect.width / ItemsVisibleAtOnce; currentSize = _listContainerRectTransform.rect.width / _itemsCount; } else { size = _scrollRectTransform.rect.height / ItemsVisibleAtOnce; currentSize = _listContainerRectTransform.rect.height / _itemsCount; } _itemSize = size; if (LinkScrolrectScrollSensitivity) { _scroll_rect.scrollSensitivity = _itemSize; } if (AutoLayoutItems && currentSize != size && _itemsCount > 0) { if (direction == ScrollSnap.ScrollDirection.Horizontal) { foreach (var tr in _listContainerTransform) { GameObject child = ((Transform)tr).gameObject; if (child.activeInHierarchy) { var childLayout = child.GetComponent<LayoutElement>(); if (childLayout == null) { childLayout = child.AddComponent<LayoutElement>(); } childLayout.minWidth = _itemSize; } } } else { foreach (var tr in _listContainerTransform) { GameObject child = ((Transform)tr).gameObject; if (child.activeInHierarchy) { var childLayout = child.GetComponent<LayoutElement>(); if (childLayout == null) { childLayout = child.AddComponent<LayoutElement>(); } childLayout.minHeight = _itemSize; } } } } } public void UpdateListItemPositions() { if (!_listContainerRectTransform.rect.size.Equals(_listContainerCachedSize)) { // checking how many children of list are active int activeCount = 0; foreach (var tr in _listContainerTransform) { if (((Transform)tr).gameObject.activeInHierarchy) { activeCount++; } } // if anything changed since last check reinitialize anchors list _itemsCount = 0; Array.Resize(ref _pageAnchorPositions, activeCount); if (activeCount > 0) { _pages = Mathf.Max(activeCount - ItemsVisibleAtOnce + 1, 1); if (direction == ScrollDirection.Horizontal) { // looking for list spanning range min/max _scroll_rect.horizontalNormalizedPosition = 0; _listContainerMaxPosition = _listContainerTransform.localPosition.x; _scroll_rect.horizontalNormalizedPosition = 1; _listContainerMinPosition = _listContainerTransform.localPosition.x; _listContainerSize = _listContainerMaxPosition - _listContainerMinPosition; for (var i = 0; i < _pages; i++) { _pageAnchorPositions[i] = new Vector3( _listContainerMaxPosition - _itemSize * i, _listContainerTransform.localPosition.y, _listContainerTransform.localPosition.z ); } } else { //Debug.Log ("-------------looking for list spanning range----------------"); // looking for list spanning range _scroll_rect.verticalNormalizedPosition = 1; _listContainerMinPosition = _listContainerTransform.localPosition.y; _scroll_rect.verticalNormalizedPosition = 0; _listContainerMaxPosition = _listContainerTransform.localPosition.y; _listContainerSize = _listContainerMaxPosition - _listContainerMinPosition; for (var i = 0; i < _pages; i++) { _pageAnchorPositions[i] = new Vector3( _listContainerTransform.localPosition.x, _listContainerMinPosition + _itemSize * i, _listContainerTransform.localPosition.z ); } } UpdateScrollbar(LinkScrolbarSteps); _startingPage = Mathf.Min(_startingPage, _pages); ResetPage(); } if (_itemsCount != activeCount) { PageChanged(CurrentPage()); } _itemsCount = activeCount; _listContainerCachedSize.Set(_listContainerRectTransform.rect.size.x, _listContainerRectTransform.rect.size.y); } } public void ResetPage() { if (direction == ScrollDirection.Horizontal) { _scroll_rect.horizontalNormalizedPosition = _pages > 1 ? (float)_startingPage / (float)(_pages - 1) : 0; } else { _scroll_rect.verticalNormalizedPosition = _pages > 1 ? (float)(_pages - _startingPage - 1) / (float)(_pages - 1) : 0; } } private void UpdateScrollbar(bool linkSteps) { if (linkSteps) { if (direction == ScrollDirection.Horizontal) { if (_scroll_rect.horizontalScrollbar != null) { _scroll_rect.horizontalScrollbar.numberOfSteps = _pages; } } else { if (_scroll_rect.verticalScrollbar != null) { _scroll_rect.verticalScrollbar.numberOfSteps = _pages; } } } else { if (direction == ScrollDirection.Horizontal) { if (_scroll_rect.horizontalScrollbar != null) { _scroll_rect.horizontalScrollbar.numberOfSteps = 0; } } else { if (_scroll_rect.verticalScrollbar != null) { _scroll_rect.verticalScrollbar.numberOfSteps = 0; } } } } void LateUpdate() { UpdateListItemsSize(); UpdateListItemPositions(); if (_lerp) { UpdateScrollbar(false); _listContainerTransform.localPosition = Vector3.Lerp(_listContainerTransform.localPosition, _lerpTarget, 7.5f * Time.deltaTime); if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 0.001f) { _listContainerTransform.localPosition = _lerpTarget; _lerp = false; UpdateScrollbar(LinkScrolbarSteps); } //change the info bullets at the bottom of the screen. Just for visual effect if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 10f) { PageChanged(CurrentPage()); } } if (_fastSwipeTimer) { _fastSwipeCounter++; } } private bool fastSwipe = false; //to determine if a fast swipe was performed //Function for switching screens with buttons public void NextScreen() { UpdateListItemPositions(); if (CurrentPage() < _pages - 1) { _lerp = true; _lerpTarget = _pageAnchorPositions[CurrentPage() + 1]; PageChanged(CurrentPage() + 1); } } //Function for switching screens with buttons public void PreviousScreen() { UpdateListItemPositions(); if (CurrentPage() > 0) { _lerp = true; _lerpTarget = _pageAnchorPositions[CurrentPage() - 1]; PageChanged(CurrentPage() - 1); } } //Because the CurrentScreen function is not so reliable, these are the functions used for swipes private void NextScreenCommand() { if (_pageOnDragStart < _pages - 1) { int targetPage = Mathf.Min(_pages - 1, _pageOnDragStart + ItemsVisibleAtOnce); _lerp = true; _lerpTarget = _pageAnchorPositions[targetPage]; PageChanged(targetPage); } } //Because the CurrentScreen function is not so reliable, these are the functions used for swipes private void PrevScreenCommand() { if (_pageOnDragStart > 0) { int targetPage = Mathf.Max(0, _pageOnDragStart - ItemsVisibleAtOnce); _lerp = true; _lerpTarget = _pageAnchorPositions[targetPage]; PageChanged(targetPage); } } //returns the current screen that the is seeing public int CurrentPage() { float pos; if (direction == ScrollDirection.Horizontal) { pos = _listContainerMaxPosition - _listContainerTransform.localPosition.x; pos = Mathf.Clamp(pos, 0, _listContainerSize); } else { pos = _listContainerTransform.localPosition.y - _listContainerMinPosition; pos = Mathf.Clamp(pos, 0, _listContainerSize); } float page = pos / _itemSize; return Mathf.Clamp(Mathf.RoundToInt(page), 0, _pages); } /// <summary> /// Added to provide a uniform interface for the ScrollBarHelper /// </summary> public void SetLerp(bool value) { _lerp = value; } public void ChangePage(int page) { if (0 <= page && page < _pages) { _lerp = true; _lerpTarget = _pageAnchorPositions[page]; PageChanged(page); } } //changes the bullets on the bottom of the page - pagination private void PageChanged(int currentPage) { _startingPage = currentPage; if (NextButton) { NextButton.interactable = currentPage < _pages - 1; } if (PrevButton) { PrevButton.interactable = currentPage > 0; } if (onPageChange != null) { onPageChange(currentPage); } } #region Interfaces public void OnBeginDrag(PointerEventData eventData) { UpdateScrollbar(false); _fastSwipeCounter = 0; _fastSwipeTimer = true; _positionOnDragStart = eventData.position; _pageOnDragStart = CurrentPage(); } public void OnEndDrag(PointerEventData eventData) { _startDrag = true; float change = 0; if (direction == ScrollDirection.Horizontal) { change = _positionOnDragStart.x - eventData.position.x; } else { change = -_positionOnDragStart.y + eventData.position.y; } if (UseFastSwipe) { fastSwipe = false; _fastSwipeTimer = false; if (_fastSwipeCounter <= _fastSwipeTarget) { if (Math.Abs(change) > FastSwipeThreshold) { fastSwipe = true; } } if (fastSwipe) { if (change > 0) { NextScreenCommand(); } else { PrevScreenCommand(); } } else { _lerp = true; _lerpTarget = _pageAnchorPositions[CurrentPage()]; } } else { _lerp = true; _lerpTarget = _pageAnchorPositions[CurrentPage()]; } } public void OnDrag(PointerEventData eventData) { _lerp = false; if (_startDrag) { OnBeginDrag(eventData); _startDrag = false; } } public void StartScreenChange() { } #endregion } }