Tanoda
ScrollSnap.cs
Go to the documentation of this file.
1
12using System;
14
16{
17 [ExecuteInEditMode]
18 [RequireComponent(typeof(ScrollRect))]
19 [AddComponentMenu("UI/Extensions/Scroll Snap")]
20 public class ScrollSnap : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollSnap
21 {
22 // needed because of reversed behaviour of axis Y compared to X
23 // (positions of children lower in children list in horizontal directions grows when in vertical it gets smaller)
24 public enum ScrollDirection
25 {
28 }
29
30 private ScrollRect _scroll_rect;
31
32 private RectTransform _scrollRectTransform;
33
34 private Transform _listContainerTransform;
35
36 //private RectTransform _rectTransform;
37
38 private int _pages;
39
40 private int _startingPage = 0;
41
42 // anchor points to lerp to see child on certain indexes
43 private Vector3[] _pageAnchorPositions;
44
45 private Vector3 _lerpTarget;
46
47 private bool _lerp;
48
49 // item list related
50 private float _listContainerMinPosition;
51
52 private float _listContainerMaxPosition;
53
54 private float _listContainerSize;
55
56 private RectTransform _listContainerRectTransform;
57
58 private Vector2 _listContainerCachedSize;
59
60 private float _itemSize;
61
62 private int _itemsCount = 0;
63
64 // drag related
65 private bool _startDrag = true;
66
67 private Vector3 _positionOnDragStart = new Vector3();
68
69 private int _pageOnDragStart;
70
71 private bool _fastSwipeTimer = false;
72
73 private int _fastSwipeCounter = 0;
74
75 private int _fastSwipeTarget = 10;
76
77 [Tooltip("Button to go to the next page. (optional)")]
79
80 [Tooltip("Button to go to the previous page. (optional)")]
82
83 [Tooltip("Number of items visible in one page of scroll frame.")]
84 [RangeAttribute(1, 100)]
85 public int ItemsVisibleAtOnce = 1;
86
87 [Tooltip("Sets minimum width of list items to 1/itemsVisibleAtOnce.")]
88 public bool AutoLayoutItems = true;
89
90 [Tooltip("If you wish to update scrollbar numberOfSteps to number of active children on list.")]
91 public bool LinkScrolbarSteps = false;
92
93 [Tooltip("If you wish to update scrollrect sensitivity to size of list element.")]
94 public bool LinkScrolrectScrollSensitivity = false;
95
96 public Boolean UseFastSwipe = true;
97
98 public int FastSwipeThreshold = 100;
99
100 public delegate void PageSnapChange(int page);
101
103
105
106 // Use this for initialization
107 void Start()
108 {
109 _lerp = false;
110
111 _scroll_rect = gameObject.GetComponent<ScrollRect>();
112 _scrollRectTransform = gameObject.GetComponent<RectTransform>();
113 _listContainerTransform = _scroll_rect.content;
114 _listContainerRectTransform = _listContainerTransform.GetComponent<RectTransform>();
115
116 //_rectTransform = _listContainerTransform.gameObject.GetComponent<RectTransform>();
119
120 PageChanged(CurrentPage());
121
122 if (NextButton)
123 {
124 NextButton.GetComponent<Button>().onClick.AddListener(() =>
125 {
126 NextScreen();
127 });
128 }
129
130 if (PrevButton)
131 {
132 PrevButton.GetComponent<Button>().onClick.AddListener(() =>
133 {
135 });
136 }
137 if (_scroll_rect.horizontalScrollbar != null && _scroll_rect.horizontal)
138 {
139
140 var hscroll = _scroll_rect.horizontalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>();
141 hscroll.ss = this;
142 }
143 if (_scroll_rect.verticalScrollbar != null && _scroll_rect.vertical)
144 {
145 var vscroll = _scroll_rect.verticalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>();
146 vscroll.ss = this;
147 }
148 }
149
151 {
152 float size = 0;
153 float currentSize = 0;
154 if (direction == ScrollSnap.ScrollDirection.Horizontal)
155 {
156 size = _scrollRectTransform.rect.width / ItemsVisibleAtOnce;
157 currentSize = _listContainerRectTransform.rect.width / _itemsCount;
158 }
159 else
160 {
161 size = _scrollRectTransform.rect.height / ItemsVisibleAtOnce;
162 currentSize = _listContainerRectTransform.rect.height / _itemsCount;
163 }
164
165 _itemSize = size;
166
168 {
169 _scroll_rect.scrollSensitivity = _itemSize;
170 }
171
172 if (AutoLayoutItems && currentSize != size && _itemsCount > 0)
173 {
174 if (direction == ScrollSnap.ScrollDirection.Horizontal)
175 {
176 foreach (var tr in _listContainerTransform)
177 {
178 GameObject child = ((Transform)tr).gameObject;
179 if (child.activeInHierarchy)
180 {
181 var childLayout = child.GetComponent<LayoutElement>();
182
183 if (childLayout == null)
184 {
185 childLayout = child.AddComponent<LayoutElement>();
186 }
187
188 childLayout.minWidth = _itemSize;
189 }
190 }
191 }
192 else
193 {
194 foreach (var tr in _listContainerTransform)
195 {
196 GameObject child = ((Transform)tr).gameObject;
197 if (child.activeInHierarchy)
198 {
199 var childLayout = child.GetComponent<LayoutElement>();
200
201 if (childLayout == null)
202 {
203 childLayout = child.AddComponent<LayoutElement>();
204 }
205
206 childLayout.minHeight = _itemSize;
207 }
208 }
209 }
210 }
211 }
212
214 {
215 if (!_listContainerRectTransform.rect.size.Equals(_listContainerCachedSize))
216 {
217 // checking how many children of list are active
218 int activeCount = 0;
219
220 foreach (var tr in _listContainerTransform)
221 {
222 if (((Transform)tr).gameObject.activeInHierarchy)
223 {
224 activeCount++;
225 }
226 }
227
228 // if anything changed since last check reinitialize anchors list
229 _itemsCount = 0;
230 Array.Resize(ref _pageAnchorPositions, activeCount);
231
232 if (activeCount > 0)
233 {
234 _pages = Mathf.Max(activeCount - ItemsVisibleAtOnce + 1, 1);
235
236 if (direction == ScrollDirection.Horizontal)
237 {
238 // looking for list spanning range min/max
239 _scroll_rect.horizontalNormalizedPosition = 0;
240 _listContainerMaxPosition = _listContainerTransform.localPosition.x;
241 _scroll_rect.horizontalNormalizedPosition = 1;
242 _listContainerMinPosition = _listContainerTransform.localPosition.x;
243
244 _listContainerSize = _listContainerMaxPosition - _listContainerMinPosition;
245
246 for (var i = 0; i < _pages; i++)
247 {
248 _pageAnchorPositions[i] = new Vector3(
249 _listContainerMaxPosition - _itemSize * i,
250 _listContainerTransform.localPosition.y,
251 _listContainerTransform.localPosition.z
252 );
253 }
254 }
255 else
256 {
257 //Debug.Log ("-------------looking for list spanning range----------------");
258 // looking for list spanning range
259 _scroll_rect.verticalNormalizedPosition = 1;
260 _listContainerMinPosition = _listContainerTransform.localPosition.y;
261 _scroll_rect.verticalNormalizedPosition = 0;
262 _listContainerMaxPosition = _listContainerTransform.localPosition.y;
263
264 _listContainerSize = _listContainerMaxPosition - _listContainerMinPosition;
265
266 for (var i = 0; i < _pages; i++)
267 {
268 _pageAnchorPositions[i] = new Vector3(
269 _listContainerTransform.localPosition.x,
270 _listContainerMinPosition + _itemSize * i,
271 _listContainerTransform.localPosition.z
272 );
273 }
274 }
275
276 UpdateScrollbar(LinkScrolbarSteps);
277 _startingPage = Mathf.Min(_startingPage, _pages);
278 ResetPage();
279 }
280
281 if (_itemsCount != activeCount)
282 {
283 PageChanged(CurrentPage());
284 }
285
286 _itemsCount = activeCount;
287 _listContainerCachedSize.Set(_listContainerRectTransform.rect.size.x, _listContainerRectTransform.rect.size.y);
288 }
289
290 }
291
292 public void ResetPage()
293 {
294 if (direction == ScrollDirection.Horizontal)
295 {
296 _scroll_rect.horizontalNormalizedPosition = _pages > 1 ? (float)_startingPage / (float)(_pages - 1) : 0;
297 }
298 else
299 {
300 _scroll_rect.verticalNormalizedPosition = _pages > 1 ? (float)(_pages - _startingPage - 1) / (float)(_pages - 1) : 0;
301 }
302 }
303
304 private void UpdateScrollbar(bool linkSteps)
305 {
306 if (linkSteps)
307 {
308 if (direction == ScrollDirection.Horizontal)
309 {
310 if (_scroll_rect.horizontalScrollbar != null)
311 {
312 _scroll_rect.horizontalScrollbar.numberOfSteps = _pages;
313 }
314 }
315 else
316 {
317 if (_scroll_rect.verticalScrollbar != null)
318 {
319 _scroll_rect.verticalScrollbar.numberOfSteps = _pages;
320 }
321 }
322 }
323 else
324 {
325 if (direction == ScrollDirection.Horizontal)
326 {
327 if (_scroll_rect.horizontalScrollbar != null)
328 {
329 _scroll_rect.horizontalScrollbar.numberOfSteps = 0;
330 }
331 }
332 else
333 {
334 if (_scroll_rect.verticalScrollbar != null)
335 {
336 _scroll_rect.verticalScrollbar.numberOfSteps = 0;
337 }
338 }
339 }
340 }
341
342 void LateUpdate()
343 {
346
347 if (_lerp)
348 {
349 UpdateScrollbar(false);
350
351 _listContainerTransform.localPosition = Vector3.Lerp(_listContainerTransform.localPosition, _lerpTarget, 7.5f * Time.deltaTime);
352
353 if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 0.001f)
354 {
355 _listContainerTransform.localPosition = _lerpTarget;
356 _lerp = false;
357
358 UpdateScrollbar(LinkScrolbarSteps);
359 }
360
361 //change the info bullets at the bottom of the screen. Just for visual effect
362 if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 10f)
363 {
364 PageChanged(CurrentPage());
365 }
366 }
367
368 if (_fastSwipeTimer)
369 {
370 _fastSwipeCounter++;
371 }
372 }
373
374 private bool fastSwipe = false; //to determine if a fast swipe was performed
375
376
377 //Function for switching screens with buttons
378 public void NextScreen()
379 {
381
382 if (CurrentPage() < _pages - 1)
383 {
384 _lerp = true;
385 _lerpTarget = _pageAnchorPositions[CurrentPage() + 1];
386
387 PageChanged(CurrentPage() + 1);
388 }
389 }
390
391 //Function for switching screens with buttons
392 public void PreviousScreen()
393 {
395
396 if (CurrentPage() > 0)
397 {
398 _lerp = true;
399 _lerpTarget = _pageAnchorPositions[CurrentPage() - 1];
400
401 PageChanged(CurrentPage() - 1);
402 }
403 }
404
405 //Because the CurrentScreen function is not so reliable, these are the functions used for swipes
406 private void NextScreenCommand()
407 {
408 if (_pageOnDragStart < _pages - 1)
409 {
410 int targetPage = Mathf.Min(_pages - 1, _pageOnDragStart + ItemsVisibleAtOnce);
411 _lerp = true;
412
413 _lerpTarget = _pageAnchorPositions[targetPage];
414
415 PageChanged(targetPage);
416 }
417 }
418
419 //Because the CurrentScreen function is not so reliable, these are the functions used for swipes
420 private void PrevScreenCommand()
421 {
422 if (_pageOnDragStart > 0)
423 {
424 int targetPage = Mathf.Max(0, _pageOnDragStart - ItemsVisibleAtOnce);
425 _lerp = true;
426
427 _lerpTarget = _pageAnchorPositions[targetPage];
428
429 PageChanged(targetPage);
430 }
431 }
432
433
434 //returns the current screen that the is seeing
435 public int CurrentPage()
436 {
437 float pos;
438
439 if (direction == ScrollDirection.Horizontal)
440 {
441 pos = _listContainerMaxPosition - _listContainerTransform.localPosition.x;
442 pos = Mathf.Clamp(pos, 0, _listContainerSize);
443 }
444 else
445 {
446 pos = _listContainerTransform.localPosition.y - _listContainerMinPosition;
447 pos = Mathf.Clamp(pos, 0, _listContainerSize);
448 }
449
450 float page = pos / _itemSize;
451
452 return Mathf.Clamp(Mathf.RoundToInt(page), 0, _pages);
453 }
454
458 public void SetLerp(bool value)
459 {
460 _lerp = value;
461 }
462
463 public void ChangePage(int page)
464 {
465 if (0 <= page && page < _pages)
466 {
467 _lerp = true;
468
469 _lerpTarget = _pageAnchorPositions[page];
470
471 PageChanged(page);
472 }
473 }
474
475 //changes the bullets on the bottom of the page - pagination
476 private void PageChanged(int currentPage)
477 {
478 _startingPage = currentPage;
479
480 if (NextButton)
481 {
482 NextButton.interactable = currentPage < _pages - 1;
483 }
484
485 if (PrevButton)
486 {
487 PrevButton.interactable = currentPage > 0;
488 }
489
490 if (onPageChange != null)
491 {
492 onPageChange(currentPage);
493 }
494 }
495
496 #region Interfaces
497 public void OnBeginDrag(PointerEventData eventData)
498 {
499 UpdateScrollbar(false);
500
501 _fastSwipeCounter = 0;
502 _fastSwipeTimer = true;
503
504 _positionOnDragStart = eventData.position;
505 _pageOnDragStart = CurrentPage();
506 }
507
508 public void OnEndDrag(PointerEventData eventData)
509 {
510 _startDrag = true;
511 float change = 0;
512
513 if (direction == ScrollDirection.Horizontal)
514 {
515 change = _positionOnDragStart.x - eventData.position.x;
516 }
517 else
518 {
519 change = -_positionOnDragStart.y + eventData.position.y;
520 }
521
522 if (UseFastSwipe)
523 {
524 fastSwipe = false;
525 _fastSwipeTimer = false;
526
527 if (_fastSwipeCounter <= _fastSwipeTarget)
528 {
529 if (Math.Abs(change) > FastSwipeThreshold)
530 {
531 fastSwipe = true;
532 }
533 }
534 if (fastSwipe)
535 {
536 if (change > 0)
537 {
538 NextScreenCommand();
539 }
540 else
541 {
542 PrevScreenCommand();
543 }
544 }
545 else
546 {
547 _lerp = true;
548 _lerpTarget = _pageAnchorPositions[CurrentPage()];
549 }
550 }
551 else
552 {
553 _lerp = true;
554 _lerpTarget = _pageAnchorPositions[CurrentPage()];
555 }
556 }
557
558 public void OnDrag(PointerEventData eventData)
559 {
560 _lerp = false;
561
562 if (_startDrag)
563 {
564 OnBeginDrag(eventData);
565 _startDrag = false;
566 }
567 }
568
569 public void StartScreenChange() { }
570 #endregion
571 }
572}
Es.InkPainter.Math Math
Definition: PaintTest.cs:7
UnityEngine.UI.Button Button
Definition: Pointer.cs:7
void OnEndDrag(PointerEventData eventData)
Definition: ScrollSnap.cs:508
delegate void PageSnapChange(int page)
void SetLerp(bool value)
Added to provide a uniform interface for the ScrollBarHelper
Definition: ScrollSnap.cs:458
void OnDrag(PointerEventData eventData)
Definition: ScrollSnap.cs:558
void OnBeginDrag(PointerEventData eventData)
Definition: ScrollSnap.cs:497
Credit Erdener Gonenc - @PixelEnvision.