572 lines
19 KiB
C#
572 lines
19 KiB
C#
|
/// Credit BinaryX
|
||
|
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602
|
||
|
/// Updated by ddreaper - 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 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
|
||
|
}
|
||
|
}
|