Search Unity

Scrolling Scroll Rect with Buttons

Discussion in 'UGUI & TextMesh Pro' started by erebel55, Jan 29, 2015.

  1. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    I am trying to create something like the following for my level selection


    (taken from bing)

    So, I want to allow the player to scroll through the levels by clicking/holding the left/right arrows.

    By using Scroll Rect I can create something like this easily. However, I don't see a way to implement the buttons instead of a drag or scrollbar.

    I tried creating two Buttons at a higher level than the Scroll Rect (so they aren't scrolled out of view as well).

    However, I don't see what function I can call in my On Click in order to scroll the Scroll Rect.

    Anyone know if this is possible?

    Thank you
     

    Attached Files:

    Last edited: Jan 29, 2015
  2. thormond

    thormond

    Joined:
    May 11, 2013
    Posts:
    33
    Try adding Scrollbar, hide it's graphic components and do it via Scrollbar.value with code, if the Value is 0 then you switch your left button to .interactive = false, and if value is 1 then you do that with your right button

    I know it's a hack but maybe that will do :D
     
  3. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Gah, second try ;-(
    Right, you can't connect to the Scrollbar control as it does not expose publicly any methods or properties to alter the position of it's content. However by delving in to the ScrollBar's code (nice we can do this now) you can see all it does is to alter the transform position of the Content GO.

    So to make this move using the buttons, just create a script that has a method to alter the transform.position of the GO it is attached to. Then attach the script to the Content GO. Finally wire the button Click event to the Content object and select the new method you just created in the script (I also added a parameter so I could control from the click the amount and direction by using either positive or negative values) using either the editor or from code.

    Now I would recommend extending this example to also adhere to the bounds of the ScrollRect in the same way the ScrollRect code does, just so that it doesn't go off screen!
    If you need me to put an example somewhere, just let me know.
     
    erebel55 likes this.
  4. thormond

    thormond

    Joined:
    May 11, 2013
    Posts:
    33
    That's all it takes (works for me):

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4.  
    5. [RequireComponent(typeof(Button))]
    6. public class ScrollbarIncrementer : MonoBehaviour
    7. {
    8.     public Scrollbar Target;
    9.     public Button TheOtherButton;
    10.     public float Step = 0.1f;
    11.  
    12.     public void Increment()
    13.     {
    14.         if (Target == null || TheOtherButton == null) throw new Exception("Setup ScrollbarIncrementer first!");
    15.         Target.value = Mathf.Clamp(Target.value + Step, 0, 1);
    16.         GetComponent<Button>().interactable = Target.value != 1;
    17.         TheOtherButton.interactable = true;
    18.     }
    19.  
    20.     public void Decrement()
    21.     {
    22.         if (Target == null || TheOtherButton == null) throw new Exception("Setup ScrollbarIncrementer first!");
    23.         Target.value = Mathf.Clamp(Target.value - Step, 0, 1);
    24.         GetComponent<Button>().interactable = Target.value != 0;;
    25.         TheOtherButton.interactable = true;
    26.     }
    27. }
    1) Copy the code to a new script
    2) Add Scrollbar
    3) Hide all Image components from Scrollbar
    4) Put that script on both buttons
    5) Add new RuntimeOnly OnClick on each button and assign them to Increment() and Decrement() functions from ScrollbarIncrementer
    6) Set Target on each script to Scrollbar and TheOtherButton to the other button
    7) works (assuming your ScrollRect is set up properly)
     
    Last edited: Jan 29, 2015
  5. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    Last edited: Jan 29, 2015
  6. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    @thormond That method looks pretty good. I'm still interested in SimonDarksideJ's method as it seems a little bit simpler. However, I did test your method out and it works fairly well. If I wanted to extend it to scroll when the buttons are held down, would I need to use an Event Trigger on the buttons and update your script to update the position until a point up event is found?
     
  7. thormond

    thormond

    Joined:
    May 11, 2013
    Posts:
    33
    You would have to add EventTrigger component and add this code while referencing OnPointerDown(either true or false on checkbox) and OnPointerUp in the inspector

    Code (CSharp):
    1.  
    2. public float HoldFrequency = 0.1f;
    3.     public void OnPointerDown(bool increment)
    4.     {
    5.         InvokeRepeating("IncrementDecrementSequence", 0.5f, HoldFrequency);
    6.     }
    7.  
    8.     public void OnPointerUp()
    9.     {
    10.         CancelInvoke("IncrementDecrementSequence");
    11.     }
    12.  
    13.     public void IncrementDecrementSequence(bool increment)
    14.     {
    15.         if(increment) Increment();
    16.         else          Decrement();
    17.     }
    or like this
    Code (CSharp):
    1. public float HoldFrequency = 0.1f;
    2.     public void OnPointerDown(bool increment)
    3.     {
    4.         StartCoroutine("IncrementDecrementSequence", increment);
    5.     }
    6.  
    7.     public void OnPointerUp()
    8.     {
    9.         StopCoroutine("IncrementDecrementSequence");
    10.     }
    11.  
    12.     IEnumerator IncrementDecrementSequence(bool increment)
    13.     {
    14.         yield return new WaitForSeconds(HoldFrequency);
    15.         if (increment) Increment();
    16.         else           Decrement();
    17.         StartCoroutine("IncrementDecrementSequence", increment);
    18.     }
     
    Last edited: Jan 29, 2015
    billdosk and erebel55 like this.
  8. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Sure, go wild (actually in testing, you don't need to control the bounds of the content, the ScrollRect won't let you!)

    Attached is a very simple demo, 1 script as follows:
    Code (CSharp):
    1. public class MoveContent : MonoBehaviour {
    2.  
    3.     public void MoveContentPane(float value)
    4.     {
    5.         var pos = transform.position;
    6.         pos.x += value;
    7.         transform.position = pos;
    8.     }
    9. }
    Which simply gives us a method to alter the position of the GO it is attached to based on the value you pass in, in this case the Congent GO of the ScrollRect.
    The buttons then simply need to hook in their "click" event to this method on the Content GO passing the value it needs to move by (positive for right and negative for left) as follows:
    upload_2015-1-29_22-8-41.png
    Very simple solution, only draw back with the Button control is that it only has a Click method. If you wanted hold as well you would need to extend the button control in your own class and add the IPointerDown interface as well.
     

    Attached Files:

  9. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    Thanks guys, these both look great :)
     
    SimonDarksideJ likes this.
  10. Supermaster

    Supermaster

    Joined:
    Mar 20, 2014
    Posts:
    1
    Thank you!!
     
  11. benfattino

    benfattino

    Joined:
    Nov 26, 2010
    Posts:
    43
    @SimonDarksideJ

    Hero of the day!
    Really thanks. This work great with menu that change the number of elements like mine because move panel all time at same distance!!!
     
  12. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    You might also want to check out the ScrollRect implementations in the UI Extensions project (link in sig). Especially the new UIVerticalScroller (only in dev code at the mo)
     
  13. TGKG

    TGKG

    Joined:
    Dec 31, 2014
    Posts:
    180

    you're brilliant, this code works great and is simple.

    3) I just moved the scroll bar off screen
     
  14. grahamlynch

    grahamlynch

    Joined:
    Jan 17, 2016
    Posts:
    3
    I have Unity 5.6.1f1 and my version of Simon's simple demo works perfectly with not many rows. However, if there is a large number of rows, the scroll rect moves correctly on the first button press but on second button press it goes back to its starting position and will not move again ( exact breakpoint not identified but it succeeds with 7 and 18 and fails with 118). I have struggled and struggled with this but for the life me I cannot see why it is doing what it does.

    The rows are held within a panel having a vertical layout and content fitter. This panel holds dynamically instantiated child panels each having a button child with horizontal layout for a number of text children each having their own layout element. I got this structure from some tutorial somewhere.

    You can see from the first press & debug output below that the scroll rect top (pivot) is moved up to 153.5 and contents are displayed correctly but the 2nd press & debug output says unity thinks the scroll rect top is at 181.5 but contents are displayed at 125.5 and the 3rd press & debug output says unity thinks the top is at 125.5 which is where displays and will move no more.

    Any advice will be much appreciated. Although familiar with coding, I am relatively new to C# & Unity so be gentle and simple! Thanks.

    the debug output is:
    before move: contents top y= 125.5 contents bottom y pos=-3094.5

    after move: contents top y pos=153.5 contents bottom y pos=-3066.5

    before move: contents top y= 153.5 contents bottom y pos=-3066.5

    after move: contents top y pos=181.5 contents bottom y pos=-3038.5

    before move: contents top y= 125.5 contents bottom y pos=-3094.5

    after move: contents top y pos=153.5 contents bottom y pos=-3066.5

    The code is below

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class Admin_Financial_Log_Scroll_Buttons_script : MonoBehaviour
    5. {
    6.     private Admin_Financial_Log_Control_script myscriptcontrol;
    7.  
    8.     private int number_of_rows;
    9.     private float row_height;
    10.  
    11.     public GameObject contents_object;
    12.     public Transform contents_pane;
    13.     private float contents_height;
    14.     private float contents_top_y;
    15.     private float contents_bottom_y;
    16.  
    17.     public GameObject viewing_object;
    18.     public Transform viewing_pane;
    19.     private float viewing_height;
    20.     private float viewing_top_y;
    21.     private float viewing_bottom_y;
    22.  
    23.     void Start ()
    24.     {
    25.         myscriptcontrol = GameObject.Find("Controlscript").GetComponent<Admin_Financial_Log_Control_script>();
    26.         viewing_object= GameObject.Find("PanelToViewLogContentsRowsScrolling");
    27.         contents_object = GameObject.Find("PanelforLogContentsRowsComplete");
    28.         myscriptcontrol.First_Time_These_Transactions = true;
    29.     }
    30.  
    31.     public void First_Time()
    32.     {
    33.         myscriptcontrol.First_Time_These_Transactions = false;
    34.         contents_pane = contents_object.GetComponent<Transform>();
    35.         contents_top_y = contents_pane.position.y;
    36.         contents_height = contents_pane.GetComponent<RectTransform>().rect.height;
    37.         contents_bottom_y = contents_top_y - contents_height;
    38.  
    39.         viewing_pane = viewing_object.GetComponent<Transform>();
    40.         viewing_top_y = viewing_pane.position.y;//pivot=1
    41.         viewing_height = viewing_pane.GetComponent<RectTransform>().rect.height;
    42.         viewing_bottom_y = viewing_pane.position.y-viewing_height;//pivot=1
    43.    
    44.         number_of_rows = myscriptcontrol.mytransactionslist.Count + 1; //incl grand totals
    45.         if (myscriptcontrol.show_content_text.text == "Items") { number_of_rows -= 1; }
    46.         row_height = contents_height / number_of_rows;
    47.  
    48.     }
    49.  
    50.     public void MoveContentPane(float updown) //-1=up ie panel moves downwards whilst +1=down ie panel moves upwards
    51.     {
    52.         if (myscriptcontrol.First_Time_These_Transactions == true) { First_Time(); } //set viewing panes details
    53.  
    54.         contents_pane = contents_object.GetComponent<Transform>();
    55.         var pos = contents_pane.position;
    56.         Debug.Log("before move: contents top y= "+ contents_pane.position.y + " contents bottom y pos=" + (contents_pane.position.y-contents_height));
    57.  
    58.         pos.y += row_height * updown;
    59.         contents_bottom_y = pos.y - contents_height;
    60.  
    61.         if (  contents_bottom_y > viewing_bottom_y ) //ie bottom of scroll panel above bottom of viewing panel
    62.         {
    63.             StartCoroutine( myscriptcontrol.ScrollbarBottom());
    64.             Debug.Log("bottom boundary gap : 'contents bottom' would be="+ contents_bottom_y + " and bottom limit="+viewing_bottom_y);
    65.             return;
    66.         }
    67.        
    68.         if(pos.y < viewing_top_y)
    69.         {
    70.             StartCoroutine(myscriptcontrol.ScrollbarTop());
    71.             Debug.Log("top boundary gap : 'contents top' would be=" + contents_top_y + " and top limit=" + viewing_top_y);
    72.             return;
    73.         }
    74.  
    75.         contents_pane.position = pos ;  
    76.    
    77.         Debug.Log("after move:  contents top y pos=" + contents_pane.position.y+" contents bottom y pos="+contents_bottom_y);
    78.     }
    79. }
    80.  
     
  15. grahamlynch

    grahamlynch

    Joined:
    Jan 17, 2016
    Posts:
    3
    Update to above....
    I have replaced the scrollbar with a slider with the result that both slider and up/down buttons now work at all times. This is useful because with a large number of entries the slider moves the viewing panel up/down by many rows whilst the buttons move it up/down by only one.

    It seems to me that the Unity scrollbar/scrollrect script can cause a problem with 'position' in certain circumstances like mine.

    If you are interested in how the buttons and slider now work - I deleted the vertical scrollbar and created a slider in its place. In the inspector I set the sliders "onvaluechanged" command to invoke the "passthrough" method shown below. The script is attached to the slider and it finds the relevant scroll rect when it starts

    The button script is also shown below and is invoked by the usual "oncommandclick" with the up button passing through 1 and the down button passing through -1.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4.  
    5. public class Admin_Financial_Log_SliderBar_script : MonoBehaviour
    6. {
    7.  
    8.     public ScrollRect MyScrollRect;
    9.  
    10.     private void Start()
    11.     {
    12.         MyScrollRect = GameObject.Find("PanelToViewLogContentsRowsScrolling").GetComponent<ScrollRect>();
    13.     }
    14.  
    15.     public void PassThrough(float scrollValue)
    16.     {
    17.         MyScrollRect.verticalNormalizedPosition = scrollValue;
    18.     }
    19. }
    20.  
    21. using UnityEngine;
    22. using UnityEngine.UI;
    23. public class Admin_Financial_Log_SliderButtons_script : MonoBehaviour
    24. {
    25.     private Admin_Financial_Log_Control_script myscriptcontrol;
    26.     private int number_of_rows;
    27.     private float row_height;
    28.     private GameObject vertical_slider_object;
    29.     private Slider vertical_slider;
    30.     public GameObject contents_object;
    31.     public Transform contents_pane;
    32.     private float contents_height;
    33.     void Start()
    34.     {
    35.         myscriptcontrol = GameObject.Find("Controlscript").GetComponent<Admin_Financial_Log_Control_script>();
    36.         contents_object = GameObject.Find("PanelforLogContentsRowsComplete");
    37.         vertical_slider_object = GameObject.Find("SliderVerticalContents");
    38.         myscriptcontrol.First_Time_These_Transactions = true;
    39.     }
    40.     public void First_Time()
    41.     {
    42.         vertical_slider = vertical_slider_object.GetComponent<Slider>();
    43.         myscriptcontrol.First_Time_These_Transactions = false;
    44.         contents_pane = contents_object.GetComponent<Transform>();
    45.         contents_height = contents_pane.GetComponent<RectTransform>().rect.height;
    46.         number_of_rows = myscriptcontrol.mytransactionslist.Count + 1; //incl grand totals
    47.         if (myscriptcontrol.show_content_text.text == "Items") { number_of_rows -= 1; }
    48.         row_height = contents_height / number_of_rows;
    49.     }
    50.     public void MoveContentPane(float updown) //1=slider moves up an -1=down
    51.     {
    52.         if (myscriptcontrol.First_Time_These_Transactions == true) { First_Time(); } //set row height
    53.         var slider = vertical_slider_object.GetComponent<Slider>();
    54.         slider.value += row_height * updown/contents_height;
    55.         vertical_slider_object.GetComponent<Slider>().value = slider.value;
    56.    }
    57. }
    58.  
     
  16. mholmes

    mholmes

    Joined:
    Dec 8, 2012
    Posts:
    414
    Im having an issue getting my scrollbar to even work. Code below builds scroll view

    Code (CSharp):
    1.  private void Build_Recipe_List()
    2.     {
    3.         string uri = Game_Data._EndPoint ;
    4.  
    5.         _User.Email = Game_Data._Rune_Master_User.Email;
    6.         StartCoroutine(RecipeDataWebRequest(uri, _User));
    7.  
    8.  
    9.         foreach (DataRow dataRow in _Recipe_Data.Rows)
    10.         {
    11.             foreach (DataColumn Column in _Recipe_Data.Columns)
    12.             {
    13.                 bool unlocked = Convert.ToBoolean(dataRow[Column]);
    14.                 string Objname = Column.ToString();
    15.                 string recipe = string.Empty;              
    16.  
    17.                 if (unlocked)
    18.                 {
    19.                     recipe = Objname.Replace("Recipe_", "").Replace("_", " ");
    20.                     Add_Button(recipe, Objname);
    21.                 }
    22.             }
    23.         }
    24.     }
    25.  
    26.     public void Add_Button(string recipe = "", string objName = "")
    27.     {
    28.         //Create Copy of Button Template
    29.         var copy = Instantiate(_btnCraftingTemplate);
    30.  
    31.         //Add Copy to Parent Object
    32.         copy.transform.parent = _Content.transform;      
    33.  
    34.         int copyindex = _index;
    35.         copy.GetComponent<Button>().name = objName;
    36.         copy.GetComponent<Button>().image.sprite = Image_Game_Data._Scroll_Icon;
    37.         copy.GetComponent<Button>().onClick.AddListener(() => { Debug.Log("index number " + copyindex); });
    38.         copy.GetComponentInChildren<Text>().text = recipe;
    39.         _index++;
    40.     }

    Click event on buttons:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.EventSystems;
    6. using UnityEngine.UI;
    7.  
    8. [RequireComponent(typeof(Button))]
    9. public class btnScroll : MonoBehaviour
    10. {
    11.     public Scrollbar Target;
    12.     public Button TheOtherButton;
    13.     public float Step = 0.1f;
    14.  
    15.     public void Increment()
    16.     {
    17.         if (Target == null || TheOtherButton == null) throw new Exception("Setup ScrollbarIncrementer first!");
    18.         Target.value = Mathf.Clamp(Target.value + Step, 0, 1);
    19.         GetComponent<Button>().interactable = Target.value != 1;
    20.         TheOtherButton.interactable = true;
    21.     }
    22.  
    23.     public void Decrement()
    24.     {
    25.         if (Target == null || TheOtherButton == null) throw new Exception("Setup ScrollbarIncrementer first!");
    26.         Target.value = Mathf.Clamp(Target.value - Step, 0, 1);
    27.         GetComponent<Button>().interactable = Target.value != 0; ;
    28.         TheOtherButton.interactable = true;
    29.     }
    30. }
     
  17. mohit-mittal

    mohit-mittal

    Joined:
    Sep 21, 2018
    Posts:
    1
    radiantboy and andreiagmu like this.
  18. radiantboy

    radiantboy

    Joined:
    Nov 21, 2012
    Posts:
    1,633
    great script, though it seems to not step perfectly between the items for me (ie, i press right, it moves but the right element is centralised), . any ideas why? Im assuming I need to adjust step, but not sure what its based on.
     
    Last edited: Mar 15, 2022
  19. MontanaAnton

    MontanaAnton

    Joined:
    Feb 16, 2014
    Posts:
    14
    I found solution here:
    https://stackoverflow.com/questions...manual-scrolling-through-script-unity-c-sharp
    and little bit modify

    Put this script on Scroll Rect
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6.  
    7. public class ScrollSnapHelper : MonoBehaviour
    8. {
    9.     public Coroutine lastLerpCoroutine;
    10.     ScrollRect scroll;
    11.     public void Start()
    12.     {
    13.         scroll = GetComponent<ScrollRect>();
    14.     }
    15.  
    16.     public void CancelLerp()
    17.     {
    18.         if (lastLerpCoroutine != null)
    19.         {
    20.             StopCoroutine(lastLerpCoroutine);
    21.             lastLerpCoroutine = null;
    22.         }
    23.     }
    24.     public void LerpToSelectedItem(RectTransform selectedRect)
    25.     {
    26.         CancelLerp();
    27.         lastLerpCoroutine =  StartCoroutine(LerpToPage(selectedRect));
    28.     }
    29.     private IEnumerator LerpToPage(RectTransform selectedRect)
    30.     {
    31.         Vector2 lerpTo = (Vector2)scroll.transform.InverseTransformPoint(scroll.content.position) - (Vector2)scroll.transform.InverseTransformPoint(selectedRect.position);
    32.         bool lerp = true;
    33.         Canvas.ForceUpdateCanvases();
    34.  
    35.         while (lerp)
    36.         {
    37.             float decelerate = Mathf.Min(10f * Time.deltaTime, 1f);
    38.             scroll.content.anchoredPosition = Vector2.Lerp(scroll.transform.InverseTransformPoint(scroll.content.position), lerpTo, decelerate);
    39.             if (Vector2.SqrMagnitude((Vector2)scroll.transform.InverseTransformPoint(scroll.content.position) - lerpTo) < 0.25f)
    40.             {
    41.                 scroll.content.anchoredPosition = lerpTo;
    42.                 lerp = false;
    43.             }
    44.             yield return null;
    45.         }
    46.     }
    47. }
    48.  

    Put this script on selectable scroll items. Dont forget to select first item in event system

    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class ScrollSelectableSnap : MonoBehaviour, ISelectHandler, IPointerEnterHandler, IPointerExitHandler
    7. {
    8.     public bool isTouching;
    9.     private ScrollSnapHelper snapHelper;
    10.     private RectTransform rectTransform;
    11.  
    12.     void Start()
    13.     {
    14.         rectTransform = transform as RectTransform;
    15.         snapHelper = transform.GetComponentInParent<ScrollSnapHelper>();    
    16.     }
    17.     public void OnSelect(BaseEventData eventData)
    18.     {
    19.         if (isTouching) return;
    20.         snapHelper.LerpToSelectedItem(rectTransform);
    21.     }
    22.  
    23.  
    24.     public void OnPointerEnter(PointerEventData eventData)
    25.     {
    26.         snapHelper.CancelLerp();
    27.         isTouching = true;
    28.     }
    29.  
    30.     public void OnPointerExit(PointerEventData eventData)
    31.     {
    32.         isTouching = false;
    33.     }
    34. }
    35.  
    36.  
     
    Last edited: Oct 17, 2022
    radiantboy likes this.
  20. MontanaAnton

    MontanaAnton

    Joined:
    Feb 16, 2014
    Posts:
    14