com.unity.uiextensions/Scripts/Layout/ScrollSnap.cs

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
}
}