Search Unity

UI Snap Solution with GridLayoutGroup for ScrollRect

Discussion in 'UGUI & TextMesh Pro' started by Polymorphik, May 7, 2015.

  1. Polymorphik

    Polymorphik

    Joined:
    Jul 25, 2014
    Posts:
    599
    Hello,

    So I am currently working on a game, and I wanted to use a ScrollRect to view items with a mask. It was really easy to setup with Unity's new UI, the only problem is it doesn't support Snapping to the closest viewing element. So I had to solve this on my anyways, if someone is looking for a solution you can try mine and see if it works for you.

    UIScrollRectSnap.cs attach this to the ScrollRect and make sure the OnValueChanged is assigned to the script. Give it a reference to the GridLayoutGroup and it would handle the rest. Work for Horizontal and Vertical individually not together, I don't think...
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4.  
    5. [RequireComponent(typeof(ScrollRect))]
    6. public class UIScrollRectSnap : MonoBehaviour {
    7.     [SerializeField] GridLayoutGroup gridLayoutGroup;
    8.     [SerializeField] int index = 0;
    9.     [SerializeField] bool lockByIndex = false;
    10.     [SerializeField] float minSpeed = 80.0f;
    11.     [SerializeField] float snapSpeed = 8.0f;
    12.  
    13.     ScrollRect scrollRect;
    14.     Vector2 target = Vector2.zero;
    15.  
    16.     void Start() {
    17.         this.scrollRect = this.GetComponent<ScrollRect>(); // Cache the scroll rect
    18.         this.Index(0); // Set the starting view element to the first one.
    19.         this.scrollRect.normalizedPosition = this.NormalizedPosition * this.index; // Set the normalization
    20.     }
    21.  
    22.     void Update() {
    23.         // Clamp by getting changes in the index.
    24.         if(this.lockByIndex == true) {
    25.             Vector2 target = this.NormalizedPosition * this.index;
    26.            
    27.             if(this.scrollRect.normalizedPosition != target)
    28.                 this.scrollRect.normalizedPosition = Vector2.Lerp(this.scrollRect.normalizedPosition, target, this.snapSpeed * Time.deltaTime);
    29.         } else {
    30.             if(this.scrollRect.velocity.magnitude <= this.minSpeed) {
    31.                 if(this.scrollRect.normalizedPosition != target)
    32.                     this.scrollRect.normalizedPosition = Vector2.Lerp(this.scrollRect.normalizedPosition, this.target, this.snapSpeed * Time.deltaTime);
    33.             }
    34.         }
    35.     }
    36.  
    37.     // The size for a single cell (element)
    38.     Vector2 SingleCell {
    39.         get {
    40.             return new Vector2(this.gridLayoutGroup.cellSize.x + this.gridLayoutGroup.spacing.x, this.gridLayoutGroup.cellSize.y + this.gridLayoutGroup.spacing.y);
    41.         }
    42.     }
    43.  
    44.     // The dimensions of the ScrollRect
    45.     Vector2 ScrollRectDimension {
    46.         get {
    47.             return new Vector2(this.scrollRect.GetComponent<RectTransform>().rect.width, this.scrollRect.GetComponent<RectTransform>().rect.height);
    48.         }
    49.     }
    50.  
    51.     // The total size of the elements in the X and Y
    52.     Vector2 ElementSize {
    53.         get {
    54.             return new Vector2(this.gridLayoutGroup.cellSize.x * (float)this.gridLayoutGroup.transform.childCount, this.gridLayoutGroup.cellSize.y * (float)this.gridLayoutGroup.transform.childCount);
    55.         }
    56.     }
    57.  
    58.     // The delta of both the X an Y the ScrollView RecTransfrom understands.
    59.     Vector2 TotalDelta {
    60.         get {
    61.             return new Vector2(this.ElementSize.x + this.gridLayoutGroup.padding.left + this.gridLayoutGroup.padding.right + (float)(this.gridLayoutGroup.transform.childCount - 1) * this.gridLayoutGroup.spacing.x, this.ElementSize.y + this.gridLayoutGroup.padding.top + this.gridLayoutGroup.padding.bottom + (float)(this.gridLayoutGroup.transform.childCount - 1) * this.gridLayoutGroup.spacing.y) - this.ScrollRectDimension;
    62.         }
    63.     }
    64.  
    65.     // The position of the element normalized
    66.     Vector2 NormalizedPosition {
    67.         get {
    68.             return new Vector2(this.SingleCell.x / this.TotalDelta.x, this.SingleCell.y / this.TotalDelta.y);
    69.         }
    70.     }
    71.  
    72.     int Elements {
    73.         get {
    74.             // How many elements in the scroll view GridLayout.
    75.             return this.gridLayoutGroup.transform.childCount;
    76.         }
    77.     }
    78.  
    79.     public void OnValueChanged(Vector2 normalized) {
    80.         if(this.scrollRect.horizontal == true) {
    81.             int elementIndex = 0;
    82.  
    83.             float distance = Mathf.Abs(this.scrollRect.normalizedPosition.x - (this.NormalizedPosition.x) * elementIndex);
    84.  
    85.             // Find the closest target to the current normalization
    86.             for(int i = 0 ; i < this.Elements; i++) {
    87.                 float possibleDistance = Mathf.Abs(this.scrollRect.normalizedPosition.x - this.NormalizedPosition.x * i);
    88.  
    89.                 if(possibleDistance < distance) {
    90.                     elementIndex = i;
    91.                     distance = possibleDistance;
    92.                 }
    93.             }
    94.  
    95.             // View the element at.
    96.             this.Index(elementIndex);
    97.  
    98.             // Set the target normalization to...
    99.             this.target = this.NormalizedPosition * this.index;
    100.         }
    101.  
    102.         if(this.scrollRect.vertical == true) {
    103.             int elementIndex = 0;
    104.            
    105.             float distance = Mathf.Abs(this.scrollRect.normalizedPosition.y - (this.NormalizedPosition.y) * elementIndex);
    106.  
    107.             // Find the closest target to the current normalization
    108.             for(int i = 0 ; i < this.Elements; i++) {
    109.                 float possibleDistance = Mathf.Abs(this.scrollRect.normalizedPosition.y - this.NormalizedPosition.y * i);
    110.                
    111.                 if(possibleDistance < distance) {
    112.                     elementIndex = i;
    113.                     distance = possibleDistance;
    114.                 }
    115.             }
    116.  
    117.             // View the element at...
    118.             this.Index(elementIndex);
    119.  
    120.             // Set the target normalization to...
    121.             this.target = this.NormalizedPosition * this.index;
    122.         }
    123.     }
    124.  
    125.     // Manually change the index element we should be looking at (great for GamePads)
    126.     public void Index(int index) {
    127.         this.index = index;
    128.  
    129.         this.index = Mathf.Clamp(this.index, 0, this.Elements);
    130.     }
    131.  
    132.     // Lock the ScrollView by only snapping by changes in the Index (great for GamePads)
    133.     public void LockByIndex(bool lockByIndex) {
    134.         this.lockByIndex = lockByIndex;
    135.     }
    136. }
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Probably belongs over in the UI forum. As a general rule of thumb if there is any code in it its not general discussion material.
     
  3. DottorFeelgood

    DottorFeelgood

    Joined:
    Jan 5, 2015
    Posts:
    5
    Hello!

    Check this out!

     
    Polymorphik likes this.
  4. Polymorphik

    Polymorphik

    Joined:
    Jul 25, 2014
    Posts:
    599
    Looks awesome
     
  5. mangax

    mangax

    Joined:
    Jul 17, 2013
    Posts:
    336
    Hello, your asset is great! elegantly made script!
    and works as expected for elements snap.

    there are many scripts for snapping elements in horizontal or vertical.
    but since you are using GridLayoutGroup i thought it would work on snapping when i have like a content grid view of 3x3 elements displayed. but it didn't work...

    am looking for a solution that it can snap to nearest row (in case if it is vertical snap) or nearest column (in case horizontal)..regardless of how many elements shown.
    i tried editing some stuff in script but i am not sure which part i should tweak to get it to work!

    any help i'll be Very thankful!