Search Unity

Scroll Rect and Scroll Bar - Arrow keys control

Discussion in 'UGUI & TextMesh Pro' started by SilverM, Jul 9, 2015.

  1. SilverM

    SilverM

    Joined:
    Jun 29, 2015
    Posts:
    5
    So far you can control the scroll rect by pressing the mouse left button down and draging or using the middle mouse scroll. I don't really see an option to use arrow keys and its something that i need. Does this feature exist and if it doesn't how can I?
     
  2. SilverM

    SilverM

    Joined:
    Jun 29, 2015
    Posts:
    5
    Anyone?
     
  3. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    This worked for me. Just add this script to your scroll rect and it may work. *fingers crossed* :p

    It works by checking if the currently selected UI element is below or above what's currently visible inside your scroll rect. If it is we snap the scroll rect to the position of that UI element. Works perfectly for my use case. I will release my entire project in a few days if you can't get it to work.

    Edit: The most important and perhaps only requirement for this to work is that the elements inside the scrollrect has their anchors set to the upper left corner.

    Edit2: This triggers for the mouse pointer as well now. Should probably make it only trigger for gamepad/keyboard. I will see if I will bother with that or not.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using System.Collections;
    5.  
    6. public class ScrollRectPosition : MonoBehaviour {
    7.  
    8.     RectTransform scrollRectTransform;
    9.     RectTransform contentPanel;
    10.     RectTransform selectedRectTransform;
    11.     GameObject lastSelected;
    12.  
    13.     void Start() {
    14.         scrollRectTransform = GetComponent<RectTransform>();
    15.         contentPanel = GetComponent<ScrollRect>().content;
    16.     }
    17.  
    18.     void Update() {
    19.         // Get the currently selected UI element from the event system.
    20.         GameObject selected = EventSystem.current.currentSelectedGameObject;
    21.  
    22.         // Return if there are none.
    23.         if (selected == null) {
    24.             return;
    25.         }
    26.         // Return if the selected game object is not inside the scroll rect.
    27.         if (selected.transform.parent != contentPanel.transform) {
    28.             return;
    29.         }
    30.         // Return if the selected game object is the same as it was last frame,
    31.         // meaning we haven't moved.
    32.         if (selected == lastSelected) {
    33.             return;
    34.         }
    35.  
    36.         // Get the rect tranform for the selected game object.
    37.         selectedRectTransform = selected.GetComponent<RectTransform>();
    38.         // The position of the selected UI element is the absolute anchor position,
    39.         // ie. the local position within the scroll rect + its height if we're
    40.         // scrolling down. If we're scrolling up it's just the absolute anchor position.
    41.         float selectedPositionY = Mathf.Abs(selectedRectTransform.anchoredPosition.y) + selectedRectTransform.rect.height;
    42.  
    43.         // The upper bound of the scroll view is the anchor position of the content we're scrolling.
    44.         float scrollViewMinY = contentPanel.anchoredPosition.y;
    45.         // The lower bound is the anchor position + the height of the scroll rect.
    46.         float scrollViewMaxY = contentPanel.anchoredPosition.y + scrollRectTransform.rect.height;
    47.  
    48.         // If the selected position is below the current lower bound of the scroll view we scroll down.
    49.         if (selectedPositionY > scrollViewMaxY) {
    50.             float newY = selectedPositionY - scrollRectTransform.rect.height;
    51.             contentPanel.anchoredPosition = new Vector2(contentPanel.anchoredPosition.x, newY);
    52.         }
    53.         // If the selected position is above the current upper bound of the scroll view we scroll up.
    54.         else if (Mathf.Abs(selectedRectTransform.anchoredPosition.y) < scrollViewMinY) {
    55.             contentPanel.anchoredPosition = new Vector2(contentPanel.anchoredPosition.x, Mathf.Abs(selectedRectTransform.anchoredPosition.y));
    56.         }
    57.  
    58.         lastSelected = selected;
    59.     }
    60. }
     
    Last edited: Aug 4, 2015
    shaneK001, ltomov and Ash-Blue like this.
  4. Ash-Blue

    Ash-Blue

    Joined:
    Aug 18, 2013
    Posts:
    102
    The answer from @TwiiK is great. But for my project I needed something a bit more optimized and with a debug view in-case anything went wrong, or needed to adjust the functionality. Say hello to AutoScroll. A simple highly optimized script that automatically adjusts the scroll view whenever a new child element is selected. Note that this script could easily be adjusted to support scrolling horizontally or in different directions.

    Screenshot 2015-08-13 09.46.40.png
    Debug view of a scrolling window (blue) with selected element highlighted.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using System.Collections;
    5.  
    6. namespace Menu {
    7.     // Detects if an immediate child is selected and out of the scroll view. If so it auto-scrolls everything into view.
    8.     public class AutoScroll : MonoBehaviour {
    9.         [SerializeField] bool debug;
    10.         [SerializeField] ScrollRect scrollRect;
    11.         [SerializeField] Scrollbar scrollbar;
    12.         [SerializeField] float scrollPadding = 20f;
    13.  
    14.         void Start () {
    15.             StartCoroutine(DetectScroll());
    16.         }
    17.  
    18.         IEnumerator DetectScroll () {
    19.             GameObject current;
    20.             GameObject prevGo = null;
    21.             Rect currentRect = new Rect();
    22.             Rect viewRect = new Rect();
    23.             RectTransform view = scrollRect.GetComponent<RectTransform>();
    24.            
    25.             while (true) {
    26.                 current = EventSystem.current.currentSelectedGameObject;
    27.                 if (current != null && current.transform.parent == transform) {
    28.                     // Get a cached instance of the RectTransform
    29.                     if (current != prevGo) {
    30.                         RectTransform rt = current.GetComponent<RectTransform>();
    31.                        
    32.                         // Create rectangles for comparison
    33.                         currentRect = GetRect(current.transform.position, rt.rect, Vector2.zero);
    34.                         viewRect = GetRect(scrollRect.transform.position, view.rect, view.offsetMax);
    35.                         Vector2 heading = currentRect.center - viewRect.center;
    36.  
    37.                         if (heading.y > 0f && !viewRect.Contains(currentRect.max)) {
    38.                             float distance = Mathf.Abs(currentRect.max.y - viewRect.max.y) + scrollPadding;
    39.                             view.anchoredPosition = new Vector2(view.anchoredPosition.x, view.anchoredPosition.y - distance);
    40.                             if (debug) Debug.LogFormat("Scroll up {0}", distance); // Decrease y value
    41.                         } else if (heading.y < 0f && !viewRect.Contains(currentRect.min)) {
    42.                             float distance = Mathf.Abs(currentRect.min.y - viewRect.min.y) + scrollPadding;
    43.                             view.anchoredPosition = new Vector2(view.anchoredPosition.x, view.anchoredPosition.y + distance);
    44.                             if (debug) Debug.LogFormat("Scroll down {0}", distance); // Increase y value
    45.                         }
    46.  
    47.                         // Get adjusted rectangle positions
    48.                         currentRect = GetRect(current.transform.position, rt.rect, Vector2.zero);
    49.                         viewRect = GetRect(scrollRect.transform.position, view.rect, view.offsetMax);
    50.                     }
    51.                 }
    52.  
    53.                 prevGo = current;
    54.                
    55.                 if (debug) {
    56.                     DrawBoundary(viewRect, Color.cyan);
    57.                     DrawBoundary(currentRect, Color.green);
    58.                 }
    59.  
    60.                 yield return null;
    61.             }
    62.         }
    63.  
    64.         static Rect GetRect (Vector3 pos, Rect rect, Vector2 offset) {
    65.             float x = pos.x + rect.xMin - offset.x;
    66.             float y = pos.y + rect.yMin - offset.y;
    67.             Vector2 xy = new Vector2(x, y);
    68.            
    69.             return new Rect(xy, rect.size);
    70.         }
    71.  
    72.         public static void DrawBoundary (Rect rect, Color color) {
    73.             Vector2 topLeft = new Vector2(rect.xMin, rect.yMax);
    74.             Vector2 bottomRight = new Vector2(rect.xMax, rect.yMin);
    75.            
    76.             Debug.DrawLine(rect.min, topLeft, color); // Top
    77.             Debug.DrawLine(rect.max, topLeft, color); // Left
    78.             Debug.DrawLine(rect.min, bottomRight, color); // Bottom
    79.             Debug.DrawLine(rect.max, bottomRight, color); // Right
    80.         }
    81.     }
    82. }
     
  5. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    Cool, will check it out when I have some time. I released my script as part of my settings menu project, but it's misbehaving on OS X in the Web Player. Haven't had the time to try and find the reason. And it's also behaving a bit funky with the mouse. Now I have some additional code to compare to. :)
     
  6. Ash-Blue

    Ash-Blue

    Joined:
    Aug 18, 2013
    Posts:
    102
    I've found the Web Player to be rather ornery when it comes to Unity 4.6 UI support. It's done everything from completely crashing my game for no reason to scrambling the UI on the screen. Usually you can get around it by implementing things with a different design pattern.
     
  7. kenncann

    kenncann

    Joined:
    Jul 16, 2015
    Posts:
    17
    Sorry to resurrect this old thread but I came up a simpler solution to this.

    I created a script to control OnSelect for each item in my list (each item in the list has a selectable class attached to it). In my script the name of each item in the list is simply its index in an array (0 to 9 for example, probably not a best practice but it's helping in multiple ways to do it this way). Then in the OnSelect function I change the value of the scrollbar to (1-thisItems#/totalItemCount):

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4.  
    5. public class ItemSelect : MonoBehaviour, ISelectHandler
    6. {
    7.     GameObject itemScrollbar;
    8.     Inventory inventory;
    9.  
    10.     void Awake()
    11.     {
    12.         itemScrollbar = GameObject.Find("ItemScrollbar");
    13.         inventory = GameObject.Find("Inventory").GetComponent<Inventory>();
    14.     }
    15.  
    16.     public void OnSelect(BaseEventData eventData)
    17.     {
    18.         //set the scrollbar position by selected item
    19.         float itemTotal = (float)inventory.items.Count;
    20.         itemScrollbar.GetComponent<Scrollbar>().value = 1.0f-(float.Parse(gameObject.name) / itemTotal);
    21.  
    22.     }
    23. }
    inventory.items is a list of the game objects populated in my UI

    Hope this helps someone.
     
  8. DTek

    DTek

    Joined:
    May 29, 2013
    Posts:
    15
    If the case of a ScrollRect oriented on the horizontal axis, you could use the following :
    Code (CSharp):
    1. myScrollRect.GetComponent<ScrollRect>().horizontalNormalizedPosition = 0.5f;
    // this takes a value from 0 to 1, where 0 is the first element displayed in the left side, and 1 is the last element displayed on the right side.
    You can calculate the position based on : amount of children the list has minus the amount of children existent on screen.
     
  9. Djaydino

    Djaydino

    Joined:
    Aug 19, 2012
    Posts:
    48
    Hi this works great, but i am trying to covert this to work for horizontal.
    But so far i only got some weird behavior, if anyone (that is better in coding than me :p ) could make a horizontal version from this?

    Thanks in advance,
    Kurt
     
  10. sudahi51

    sudahi51

    Joined:
    Aug 17, 2020
    Posts:
    7
    Hey was there any luck on the horizontal stuff?
     
  11. hedgefield

    hedgefield

    Joined:
    Jan 1, 2014
    Posts:
    39
    I'm struggling with a horizontal version of this as well, crazy that it's not just a built-in feature in Unity. Anyone had any luck making it work for horizontal scrollviews?