11 public class ScrollSnapBase : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollSnap, IPointerClickHandler
13 internal Rect panelDimensions;
14 internal RectTransform _screensContainer;
15 internal bool _isVertical;
17 internal int _screens = 1;
19 internal float _scrollStartPosition;
20 internal float _childSize;
21 private float _childPos, _maskSize;
22 internal Vector2 _childAnchorPoint;
23 internal ScrollRect _scroll_rect;
24 internal Vector3 _lerp_target;
26 internal bool _pointerDown =
false;
27 internal bool _settled =
true;
28 internal Vector3 _startPosition =
new Vector3();
29 [Tooltip(
"The currently active page")]
30 internal int _currentPage;
31 internal int _previousPage;
32 internal int _halfNoVisibleItems;
33 internal bool _moveStarted;
34 internal bool _isInfinite;
35 internal int _infiniteWindow;
36 internal float _infiniteOffset;
37 private int _bottomItem, _topItem;
46 [Tooltip(
"The screen / page to start the control on\n*Note, this is a 0 indexed array")]
50 [Tooltip(
"The distance between two pages based on page height, by default pages are next to each other")]
55 [Tooltip(
"The gameobject that contains toggles which suggest pagination. (optional)")]
58 [Tooltip(
"Button to go to the previous page. (optional)")]
61 [Tooltip(
"Button to go to the next page. (optional)")]
64 [Tooltip(
"Transition speed between pages. (optional)")]
67 [Tooltip(
"Hard Swipe forces to swiping to the next / previous page (optional)")]
70 [Tooltip(
"Fast Swipe makes swiping page next / previous (optional)")]
73 [Tooltip(
"Swipe Delta Threshold looks at the speed of input to decide if a swipe will be initiated (optional)")]
76 [Tooltip(
"Offset for how far a swipe has to travel to initiate a page change (optional)")]
79 [Tooltip(
"Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")]
82 [Tooltip(
"Threshold for swipe speed to initiate a swipe, below threshold will return to closest page (optional)")]
85 [Tooltip(
"Use time scale instead of unscaled time (optional)")]
88 [Tooltip(
"The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")]
91 [Tooltip(
"Pixel size to buffer around Mask Area. (optional)")]
106 float infWindow = (float)value / (
float)_screensContainer.childCount;
110 _infiniteWindow = (int)(
Math.Floor(infWindow));
114 _infiniteWindow = value / _screensContainer.childCount;
117 _infiniteWindow = value < 0 ? (-_infiniteWindow) : _infiniteWindow;
120 value = value % _screensContainer.childCount;
123 value = _screensContainer.childCount + value;
125 else if (value > _screensContainer.childCount - 1)
127 value = value - _screensContainer.childCount;
130 if ((value != _currentPage && value >= 0 && value < _screensContainer.childCount) || (value == 0 && _screensContainer.childCount == 0))
132 _previousPage = _currentPage;
133 _currentPage = value;
135 if (!_lerp) ScreenChange();
136 OnCurrentScreenChange(_currentPage);
141 [Tooltip(
"By default the container will lerp to the start when enabled in the scene, this option overrides this and forces it to simply jump without lerping")]
144 [Tooltip(
"By default the container will return to the original starting page when enabled, this option overrides this behaviour and stays on the current selection")]
147 [Tooltip(
"(Experimental)\nBy default, child array objects will use the parent transform\nHowever you can disable this for some interesting effects")]
150 [Tooltip(
"Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")]
154 [Tooltip(
"Event fires when a user starts to change the selection")]
159 [Tooltip(
"Event fires as the page changes, while dragging or jumping")]
160 private SelectionPageChangedEvent m_OnSelectionPageChangedEvent =
new SelectionPageChangedEvent();
164 [Tooltip(
"Event fires when the page settles after a user has dragged")]
165 private SelectionChangeEndEvent m_OnSelectionChangeEndEvent =
new SelectionChangeEndEvent();
171 if (_scroll_rect ==
null)
173 _scroll_rect = gameObject.GetComponent<ScrollRect>();
175 if (_scroll_rect.horizontalScrollbar && _scroll_rect.horizontal)
177 var hscroll = _scroll_rect.horizontalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
180 if (_scroll_rect.verticalScrollbar && _scroll_rect.vertical)
182 var vscroll = _scroll_rect.verticalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
185 panelDimensions = gameObject.GetComponent<RectTransform>().rect;
192 _screensContainer = _scroll_rect.content;
194 InitialiseChildObjects();
202 _isInfinite = GetComponent<UI_InfiniteScroll>() !=
null;
205 internal void InitialiseChildObjects()
209 if (_screensContainer.transform.childCount > 0)
211 Debug.LogError(
"ScrollRect Content has children, this is not supported when using managed Child Objects\n Either remove the ScrollRect Content children or clear the ChildObjects array");
215 InitialiseChildObjectsFromArray();
217 if (GetComponent<UI_InfiniteScroll>() !=
null)
219 GetComponent<UI_InfiniteScroll>().Init();
224 InitialiseChildObjectsFromScene();
228 internal void InitialiseChildObjectsFromScene()
230 int childCount = _screensContainer.childCount;
232 for (
int i = 0; i < childCount; i++)
234 ChildObjects[i] = _screensContainer.transform.GetChild(i).gameObject;
242 internal void InitialiseChildObjectsFromArray()
245 RectTransform childRect;
247 for (
int i = 0; i < childCount; i++)
253 childRect = child.GetComponent<RectTransform>();
254 childRect.rotation = _screensContainer.rotation;
255 childRect.localScale = _screensContainer.localScale;
256 childRect.position = _screensContainer.position;
259 child.transform.SetParent(_screensContainer.transform);
268 internal void UpdateVisible()
277 _halfNoVisibleItems = (int)
Math.Round(_maskSize / (_childSize *
MaskBuffer), MidpointRounding.AwayFromZero) / 2;
278 _bottomItem = _topItem = 0;
280 for (
int i = _halfNoVisibleItems + 1; i > 0; i--)
282 _bottomItem = _currentPage - i < 0 ? 0 : i;
283 if (_bottomItem > 0)
break;
287 for (
int i = _halfNoVisibleItems + 1; i > 0; i--)
289 _topItem = _screensContainer.childCount - _currentPage - i < 0 ? 0 : i;
290 if (_topItem > 0)
break;
302 Debug.Log(
"Failed to setactive child [" + i +
"]");
309 if (_screensContainer.childCount - _currentPage > _topItem)
ChildObjects[
CurrentPage + _topItem].SetActive(
false);
315 if (_currentPage < _screens - 1 || _isInfinite)
322 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) + 1;
328 GetPositionforPage(_currentPage, ref _lerp_target);
337 if (_currentPage > 0 || _isInfinite)
344 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) - 1;
350 GetPositionforPage(_currentPage, ref _lerp_target);
362 if (screenIndex <= _screens - 1 && screenIndex >= 0)
368 GetPositionforPage(_currentPage, ref _lerp_target);
378 internal int GetPageforPosition(Vector3 pos)
381 (int)
Math.Round((_scrollStartPosition - pos.y) / _childSize) :
382 (
int)
Math.Round((_scrollStartPosition - pos.x) / _childSize);
390 internal bool IsRectSettledOnaPage(Vector3 pos)
393 -((pos.y - _scrollStartPosition) / _childSize) == -(int)
Math.Round((pos.y - _scrollStartPosition) / _childSize) :
394 -((pos.x - _scrollStartPosition) / _childSize) == -(
int)
Math.Round((pos.x - _scrollStartPosition) / _childSize);
402 internal void GetPositionforPage(
int page, ref Vector3 target)
404 _childPos = -_childSize * page;
407 _infiniteOffset = _screensContainer.anchoredPosition.y < 0 ? -_screensContainer.sizeDelta.y * _infiniteWindow : _screensContainer.sizeDelta.y * _infiniteWindow;
408 _infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
409 target.y = _childPos + _scrollStartPosition + _infiniteOffset;
413 _infiniteOffset = _screensContainer.anchoredPosition.x < 0 ? -_screensContainer.sizeDelta.x * _infiniteWindow : _screensContainer.sizeDelta.x * _infiniteWindow;
414 _infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
415 target.x = _childPos + _scrollStartPosition + _infiniteOffset;
422 internal void ScrollToClosestElement()
425 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
426 GetPositionforPage(_currentPage, ref _lerp_target);
427 OnCurrentScreenChange(_currentPage);
433 internal void OnCurrentScreenChange(
int currentScreen)
435 ChangeBulletsInfo(currentScreen);
436 ToggleNavigationButtons(currentScreen);
443 private void ChangeBulletsInfo(
int targetScreen)
446 for (
int i = 0; i <
Pagination.transform.childCount; i++)
448 Pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i)
458 private void ToggleNavigationButtons(
int targetScreen)
470 NextButton.GetComponent<
Button>().interactable = targetScreen < _screensContainer.transform.childCount - 1;
475 private void OnValidate()
477 if (_scroll_rect ==
null)
479 _scroll_rect = GetComponent<ScrollRect>();
481 if (!_scroll_rect.horizontal && !_scroll_rect.vertical)
483 Debug.LogError(
"ScrollRect has to have a direction, please select either Horizontal OR Vertical with the appropriate control.");
485 if (_scroll_rect.horizontal && _scroll_rect.vertical)
487 Debug.LogError(
"ScrollRect has to be unidirectional, only use either Horizontal or Vertical on the ScrollRect, NOT both.");
489 var ScrollRectContent = gameObject.GetComponent<ScrollRect>().content;
490 if (ScrollRectContent !=
null)
492 var children = ScrollRectContent.childCount;
522 var infiniteScroll = GetComponent<UI_InfiniteScroll>();
525 Debug.LogError($
"[{gameObject.name}]When using procedural children with a ScrollSnap (Adding Prefab ChildObjects) and the Infinite Scroll component\nYou must set the 'InitByUser' option to true, to enable late initialising");
544 internal void ScreenChange()
552 internal void EndScreenChange()
556 _moveStarted =
false;
574 returnObject = _screensContainer.GetChild(
CurrentPage);
577 #region Drag Interfaces
587 _startPosition = _screensContainer.anchoredPosition;
594 public void OnDrag(PointerEventData eventData)
599 public virtual void OnEndDrag(PointerEventData eventData) { }
603 #region IScrollSnap Interface
608 int IScrollSnap.CurrentPage()
610 return CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
631 var position = _screensContainer.anchoredPosition;
UnityEngine.UI.Button Button
Credit Erdener Gonenc - @PixelEnvision.